1. Introduction

This document details the code used to intake, clean, geolocate and coalesce administrative data on tobacco retail licenses from three states - Pennsylvania, New Jersey and Delaware. These data will be used to relate the geo-location information provided by GeoScanning project subjects to retailer locations according to experimental parameters to be specified.

This codebase does two things:

  1. Creates an initial database of retailers which can be updated as new data are published. (This "baseline" data set consists of 31,034 retailers, of which only 10 could not be geolocated after qualitative research.)

  2. Creates a process by which new data sets - published by PA and DE using open source Application Programming Interfaces (APIs), and provided by direct communication from New Jersey - are cross-checked against existing data.

New retailers are added to the database, old ones are marked as closed, repeat entries are logged as continuing their retail activity. All observations are then geo-located and errors checked and corrected - first automatically and then manually. This updating protocol should hypothetically be reproducible.

This document is generally organized by state - with each section documenting a) the creation of a baseline data set and b) the update routine for that state.

The end of the document contains code for making interactive maps and also has a set of data dictionaries.

States can all be updated at once, but more likely they can be done one at a time - code for doing one state in a sitting (uploading the whole baseline set and just replacing one state) is included near the end of this document.

These retailer databases are designed to be integrated into a broader code base which intakes and analyzes geo-location data and relates them to the retailer information. Not only do these data have lat/lon which enable geographic relationships, but they now have a time-window associated with licnesure at that location, allowing for a second check against geo-location info to confirm "exposure" to point-of-sale tobacco locations.

During the summer and fall of 2019, Michael Fichman, city planning researcher and analyst at PennPraxis (of the Weitzman School of Design), took pre-existing codebase reltaed to geo-location data developed for the project and updated it to conform (where possible) to modern tidyverse data wrangling standards and the powerful sf spatial package which is integrated in to the tidyverse and R's powerful visualization package ggplot2.

The next step is to integrate these retailer data, in their now-complete form, to geo-location data, and integrate measures developed by NYU including "stay event" detections of sedentary behavior in addition to previously created mechanisms for spatial joins between retailer buffers and geo-location data.

At the present date, this is a document-in-progress and the code contained heirein is designed to operate on test geo data and a temporary data set of tobacco retailer locations. Subsequent steps to code base development will include the creation of a suite of descriptive statistics for standard data intake and analysis procedures, the creation of an interactive map as part of a subject intake protocol and the development of a "spatial risk surface" analytic approach. Furthermore, retailer information for a multi-state study area will need to be run through the protocol.

1.1. About this code base

This is a "flexible" code base rather than a series of tightly-wrapped functions that take a series of inputs and parameters and output an object. This type of code base is a bit harder to use without a surface understanding of R, but much more adaptable to new intake data types and structures, updates and new analyses. For example, this code will have fewer tightly written functions that can be simply executed, but are very difficult to troubleshoot in the event of bad data or changes to the programming language.

The user needs to be able to operate the R studio environment, execute code in the R studio console, install and load packages, set filepaths for uploading and exporting data, change variable and object names and perform a basic level of troubleshooting using internet forums (like Stack Overflow, github etc) and R documentation if necessary.

That said, this code is open source and subject to deprecation, package updates etc., and is therefore provided with no definitive warranty. This document is designed to annotate the code base to be understandable, reproducible and reflective of research needs.

The elements of this codebase can be used out of order - with states updated one state at a time. Furthermore, the general outline of the leaflet code in can be used at any stage of the process to check the retailer locations for gross errors and explore them more deeply.

1.2. Using This Document

This document is an R Markdown document, which is a publishing language designed embed chunks of R code. It can also be used to create reproducible reports undergirded by R code.

Code can be cut and pasted from this document into a regular R code file or the R console.

Use the table of contents at the left hand side to navigate through this document.

1.3. Workflow

  1. Create a baseline data set from previously published license data, standardize the column names and geolocate all of the observations with lat/lon using the Google geocoding API. We refer to this as a "precursor routine" and it is done for each of our three states. This code is shown here for documentation purposes, but shouldn't need to be replicated in the future.

  2. Intake new licensing information directly from PA and DE using the Socrata Open Data API (SODA) and from New Jersey through direct communication. The basic workflow here is to read in json data from the API, standardize column names to conform to our standards, convert it to sf data type, timestamp it, write it out with a date stamp for today's date along with the expiration date.

  3. Compare the licenses in the new data to those in the baseline data and log their stay in the database. If a licensee is new, create an entry for it. If a licensee no longer has a registration in the new data, mark their license as "EXPIRED". If a licensee is still found and unchanged, extend the time of its stay in the database

  4. Geolocate the retailer data by sending any address which lack lat/lon information to the Google geocoding API, which is the industry standard service.

  5. Check errors using an automated process - figuring out if there are points which are outside of the state in which they are supposed to be located. Often when there are errors with geolocated data - the errors are quite big - across the country, not just accross the street.

  6. Fix errors (bad geocodes and missing address/geo-data) manually using google maps and research using state business databases, business listings and other methods. Periodically create leaflet maps to eye-test the checking.

  7. Write out the data for each state to file.

  8. Coalesce the data into a multi-state data set and write it to file.

  9. Map the data using the leaflet web mapping package for display and use during study intake OR during the error checking process (to examine individual observations.). This leaflet map can be run at any time in order to check errors (see step 6)

2. Setup

2.1. Load Libraries

library(tidyverse)
library(sf)
library(tigris)
library(tidycensus)
library(lubridate)
library(ggmap)
library(jsonlite)
library(RSocrata)
library(readxl)
library(leaflet)
library(leaflet.providers)
library(readxl)

tidyverse - R's mutually intelligible world of data-wrangling and graphics packages including dplyr

sf - The premier spatial package for R, which plays well with ggplot2, dplyr and the other tidyverse packages. Includes geoprocessing capabilities for point and polygon geometries.

tigris - A package for calling shapefiles from the US Dept. of Commerce, including political boundaries, school districts and hydrology

tidycensus - A tidy and sf compatible package for calling the US Census API for any available data from the ACS and decennial census, 2000 to present. Includes geometries from tracts to counties to states if desired.

lubridate - A sophisticated yet simple date-handling package

ggmap - An older mapping graphics package designed for use with ggplot, which has been surpassed by sf but allows for geocoding to APIs such as Google Maps and the Data Science Toolkit.

jsonlite - A package designed to upload, convert and handle JSON files in R

RSocrata - A package used to interact with APIs commonly used to host open data sets. Pennsylvania and Delaware use "SODA" - the Socrata Open Data API as an endpoint for its data.

readxl - A package for reading Excel files to tibble/dataframe

leaflet - One of the most common javascropt/html/css mapping packages for web use. R Markdown supports leaflet functionality and does not require the use of javascript. Leaflet does not suport sf and any vector data beyond points needs to use rgdal, an older spatial package.

leaflet.providers - additional basemaps and styling options for leaflet, including ESRI data

2.2. Register Google API Key

Until recently there were several geocoders of reasonable quality that were available for free, especially for small runs. To deter developers from making endless API calls, high quality services such as the Google geodocidng API are available only for a fee.

This is a geocoding-intensive project, with thousands of observations requiring geocoding. The ggmap package allows for simple geocoding by address in R, with the google API as one of the available extensions.

You can get your own key by signing up for a google developer account, which gets you a 300 dollar starting credit. A thousand requests cost 5 dollars. This should be sufficient.

If you are running this codebase, consult with me for my API key, or get your own.

Information about using the ggmap package and the google API can be found at the 'r-bloggers' site. You can follow that link to get your own account started.

Begin by registering your key with the ggmap function register_google

register_google(key = "YOUR API KEY GOES HERE")

2.3. Create Canonical Names

We create a list of variable names which our final data sets will use. Each state uses different names to describe similar fields (e.g. latitude vs lat) and these standards can change from year to year. We will create our own data standard to which we will force each data set to conform.

The field names are somewhat self-explanatory.

canonical_names <- c("county", "trade_name", "account", "license_type",
                     "expiration_date", "lat", "lon", "address_full",
                     "publish_date", "state", "expired_y_n")

2.4. Load State Shapefiles

In order to double check our geocoded retailer locations to detect serious errors, we load shapefiles for US state boundaries from the US Census Bureau using the tigris package to interact with their API (this does not require a Census API Key). We download the complete shapefile, transform it to an sf object, and then subset each state and reproject the spatial data to WGS84 (aka Web Mercator) using the st_transform function from the sf package.

states_shp <- states()  %>%
  st_as_sf() %>%
  st_transform(crs = 4326)

pa_shp <- states_shp %>%
  filter(STUSPS == "PA")

nj_shp <- states_shp %>%
  filter(STUSPS == "NJ")

de_shp <- states_shp %>%
  filter(STUSPS == "DE")

3. Precursor Routine - Baseline PA Retailer Data

Our baseline PA data come from a shapefile downloaded by the Geoscanning team in 2018 from the Commonwealth's Open Data portal. This is a precursor to our code routine (e.g. our code takes in NEW data). The following steps do not need to be re-run, and act only as documentation for the building of the portion of our retailer database 11/2018-1/2020. Ancestral data are not published by PA, this provides a fairly good look into the recent past.

3.1. Read new data from Socrata

Steps:

  1. Grab the data from the PA SODA API - at the time (1/29/2020) the most recent set. (read.socrata)

  2. Filter out of state and vending observations

  3. Add a time stamp with the date of download (1/29/2020), a column for 'state' and coerce the license expiration date to date ('ymd')

  4. Rename relevant columns to lat, lon and address_full

  5. Use mutate_if to coerce all factors to character

  6. Replace "&" characters with "AND" (better for geocoding)

Address data are in a json-like format (in brackets etc) but this should not bother the Google geocoder.

retailers_Socrata <- read.socrata("https://data.pa.gov/resource/ut72-sft8.json") %>%
  filter(county != "UNKNOWN/OUT OF STATE",
         license_type != "Vending") %>%
  mutate(publish_date = today(tzone = "America/New_York"),
         state = "PA",
         expiration_date = ymd(expiration_date)) %>%
  rename(lat = location_1.latitude,
         lon = location_1.longitude,
         address_full = location_1.human_address) %>%
  dplyr::select(-legal_name, -postal_code, -country) %>%
  mutate_if(is.factor, as.character) %>%
  mutate(address_full = str_replace(address_full, "&", "AND"))

3.2. Read in original shapefile from 2018

The older, shapefile data, are not uniform with the data from the SODA API. We unite the address information, rename various columns to comport with newer data, extract lat/lon values from the geometry, coerce to data frame, remove geometery, convert factors to character (mutate_if).

This code probably won't be necessary to use in the future when you are using downloads from the Socrata SODA API.

retailers_1 <- st_read("//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/inputData/RetailerMaps/RetailerMaps/PA/2018_11/rows_11/rows_11.shp") %>%
  filter(location_3 == "PA",
         county != "UNKNOWN/OUT OF STATE",
         license_ty != "Vending") %>%
  mutate(location_1 = as.character(location_1), 
         location_2 = as.character(location_2), 
         location_3 = as.character(location_3), 
         postal_cod = as.character(postal_cod),
         state = "PA") %>% 
  unite(., "address_full", sep = " ",
        c("location_1", "location_2", "location_3", "postal_cod")) %>%
  dplyr::select(-computed_r, -version, -computed_1, -computed_2, -computed_3, -computed_4,
         -cartodb_id, -country, -location_4, -created_at, -id, -country, -legal_name) %>%
  rename(publish_date = updated_at,
         expiration_date = expiration,
         license_type = license_ty) %>%
  mutate(lon=map_dbl(geometry, ~st_centroid(.x)[[1]]),
         lat=map_dbl(geometry, ~st_centroid(.x)[[2]]))%>%
                  as.data.frame() %>%
                  select(-geometry) %>%
  mutate_if(is.factor, as.character)  %>% 
  mutate(address_full = str_replace(address_full, "&", "AND"))

Check to see how many NA in spatial information by summarizing.

summary(is.na(retailers_Socrata$lat))
summary(is.na(retailers_1$lat))

3.3 Coalesce Data Sets

Check to see which licenses are new in the incoming data. When we find new entries that match entries in our existing data, we update their date of expiry. If data entries in the old database are not represented in the new one, we mark them as expired.

Note the use of the dplyr version of ifelse called if_else here in order to make sure the result is a date and not numeric.

Here's what this does:

  1. Full Join of retailers from Nov 2018 (e.g. existing retailers) to retailers_Socrata (e.g. new retailers) joined by both account and trade name. Since the data sets have identical names, this produces an "x" and "y" column for each of our canonical names - x being the left side and y being the right side of the join.

  2. If there is no expiration date on file for the Socrata set (e.g. there is no entry for a retailer on file in the newest data set), assume the account is expired. Create a column expired_y_n and mark it "EXPIRED" or else "ACTIVE"

  3. If it's expired (e.g. there is no corresponding y entry for a given x entry), mark an expiration date as being the last active date of the license on file from the existing database.

  4. Create a new set of canonical columns by checking to see if both x and y columns are filled for each of our variables - if there's no x, put the y data in there and vice versa.

  5. Generate lat/lon by checking if either new or existing has missing lat/lon info, if so, take the existing one, if not, take the newest lat/lon.

  6. Use similar logic to fill in address, county, publish date and license type data.

  7. Remove the columns which are left over from the join (e.g. lat.x, license_type.y) using dplyr::select

In this join the most recent data set goes second (e.g. the "y") data set.

pa_join <- full_join(retailers_1, 
                     retailers_Socrata, by = c("account", "trade_name")) %>%
  mutate(expired_y_n = ifelse(is.na(expiration_date.y) == TRUE, "EXPIRED", "ACTIVE"),
         expiration_date = if_else(expired_y_n == 'EXPIRED', expiration_date.x, ymd(expiration_date.y)),
         lat = ifelse(is.na(lat.x) == FALSE, lat.x, lat.y),
         lon = ifelse(is.na(lon.x) == FALSE, lon.x, lon.y),
         address_full = ifelse(is.na(address_full.x) == FALSE, address_full.x, address_full.y),
         county = ifelse(is.na(county.y) == TRUE, county.x, county.y),
         publish_date = if_else(is.na(publish_date.y) == TRUE, publish_date.x, publish_date.y),
         license_type = ifelse(is.na(license_type.y) == TRUE, license_type.x, license_type.y),
         state = "PA") %>%
  dplyr::select(-lat.x, -lon.x, -lat.y, -lon.y, 
         -address_full.x, -address_full.y, -county.x, -county.y,
         -publish_date.x, -publish_date.y, -state.x, -state.y, -license_type.x, -license_type.y,
         -expiration_date.x, -expiration_date.y)

We again summarize to check on the amount of NA data.

summary(is.na(pa_join$lat))

3.4. Geocode missing information

The google geocoder takes a list of addresses and returns a list of lat/lon pairs.

  1. We create a dataframe of observations to be geocoded (to_geocode) which lack lat/lon data.

  2. We send that data to the geocoder via ggmap (geocode).

  3. We cbind the returned lat/lon pairs to the rest of the columns in the to_geocode dataframe

  4. We rbind the rows from our to_geocode set, which now have x-y info, to our data which did not need to be geocoded in the first place and create a dataframe pa_geocoded.

  5. Perform a data check to see what might still be missing

to_geocode <- pa_join %>%
  filter(is.na(lat) == TRUE)

pa_geocoded <- geocode(to_geocode$address_full, source =  "google") %>%
  cbind(., to_geocode %>%
          select(-lat, -lon)) %>%
  rbind(pa_join %>%
          filter(is.na(lat) == FALSE), .)

summary(is.na(pa_geocoded$lat))

3.5. Perform quality check for bad geocodes

When geocodes are wrong, they are often very wrong, and set somewhere wildly different from our target area. We do a spatial join with the PA shapefile, keep only the observations which fall outside of PA. We can check these observations and potentially geocode them manually.

We do this through the following steps:

  1. Perform a spatial join using st_join from the sf package to see which points from pa_geocoded fall outside PA (excluding those we know are NA at present). To do this we need to reproject our lat/lon data as an sf vector object projected in WGS84 (web mercator, crs = 4326) and join it to pa_shp.

  2. We filter those observations which come back as is.na(STUSPS) == TRUE (e.g. they don't join at all to the PA tigris shapefile and don't take on any of it's attributes like STUSPS) and know that these are the ones which fell outside our shapefile.

  3. We dplyr::select only our canonical variables from these and extract the lat/lon from their geometries using a mutate statement. We want to turn this back into a dataframe with lat/lon data and not geometries like an sf object. So we set it as.data.frame and remove the geometry column.

  4. We now rbind these data back to our pa_geocoded observations which lacked lat/lon in the first place, to create a data set consisting of all our observations which we presume have bad geocodes.

outside_PA <- st_join(pa_geocoded %>% 
          filter(is.na(lat) == FALSE) %>% 
          st_as_sf(., coords = c("lon", "lat"), crs = 4326), 
        pa_shp, 
        join = st_within, 
        left = TRUE) %>%
  filter(is.na(STUSPS) == TRUE) %>%
  dplyr::select(trade_name, account, address_full, county) %>%
  mutate(lon=map_dbl(geometry, ~st_centroid(.x)[[1]]),
         lat=map_dbl(geometry, ~st_centroid(.x)[[2]]))%>%
  as.data.frame() %>%
  dplyr::select(-geometry) %>%
  rbind(., pa_geocoded %>%
          filter(is.na(lat) == TRUE))

3.6. Manually geocode remaining observations

The observations that don't geocode will need to be dealt with manually (e.g. input lat/lon coordinates based on google map research). Similarly, observations that geocode poorly and end up on our outside_PA list need to be manually geocoded.

This has to be done by hand unfortuately. You can go through the observations which either have NA values for lat/lon in pa_geocoded or are found to be outside PA in the outside_PA dataframe. Here we do this with web research and mutate statements where we replace the lat/lon values for individual licenses.

In other data sets and later in this code we export the data to a csv and an RA does the coding, which we then re-import and integrate. The export approach is probably less prone to error from transcription problems.

pa_geocoded_fixed <- pa_geocoded %>%
  mutate(lat = ifelse(account == "51**6778", 39.948989, lat),
         lon = ifelse(account == "51**6778", -75.147568, lon)) %>%
  mutate(lat = ifelse(account == "30**1789", 39.931567, lat),
         lon = ifelse(account == "30**1789", -80.058022, lon)) %>%
  mutate(lat = ifelse(account == "70**2004", 41.341935, lat),
         lon = ifelse(account == "70**2004", -75.992409, lon)) %>%
  mutate(lat = ifelse(account == "51**8445", 39.954983, lat),
         lon = ifelse(account == "51**8445", -75.164616, lon)) %>%
  mutate(lat = ifelse(account == "70**1005", 41.017803, lat),
         lon = ifelse(account == "70**1005", -75.589636, lon)) %>%
  mutate(lat = ifelse(account == "51**3448", 39.950923, lat),
         lon = ifelse(account == "51**3448", -75.163884, lon)) %>%
  mutate(lat = ifelse(account == "64**9445", 41.463524, lat),
         lon = ifelse(account == "64**9445", -75.180809, lon))%>%
  mutate(lat = ifelse(account == "06**1675", 40.557271, lat),
         lon = ifelse(account == "06**1675", -75.977632, lon)) %>%
  mutate(lat = ifelse(account == "09**7593", 40.109250, lat),
         lon = ifelse(account == "09**7593", -74.855736, lon)) %>%
  mutate(lat = ifelse(account == "45**4142", 41.094910, lat),
         lon = ifelse(account == "45**4142", -75.327707, lon)) %>%
  mutate(lat = ifelse(account == "10**7974", 41.071094, lat),
         lon = ifelse(account == "10**7974", -80.045434, lon)) %>%
  mutate(lat = ifelse(account == "17**6746", 40.999937, lat),
         lon = ifelse(account == "17**6746", -78.347428, lon)) %>%
  mutate(lat = ifelse(account == "34**8300", 40.999937, lat),
         lon = ifelse(account == "34**8300", -78.347428, lon)) %>%
  mutate(lat = ifelse(account == "49**1271", 40.687000,  lat), 
         lon = ifelse(account == "49**1271", -76.829610, lon)) %>%
  mutate(lat = ifelse(account == "04**5721", 40.586025,   lat), 
         lon = ifelse(account == "04**5721", -80.257807, lon)) %>%
  mutate(lat = ifelse(account == "51**0499", 39.948303,    lat), 
         lon = ifelse(account == "51**0499", 42.115990,  lon)) %>%
  mutate(lat = ifelse(account == "70**5157", -80.033087,    lat), 
         lon = ifelse(account == "70**5157", -75.164633, lon)) %>% 
  mutate(lat = ifelse(account == "70**1014", 40.960844,    lat), 
         lon = ifelse(account == "70**1014", -77.762074, lon)) %>% 
  mutate(lat = ifelse(account == "70**4002", 41.027503,    lat), 
         lon = ifelse(account == "70**4002", -76.079946, lon)) %>% 
  mutate(lat = ifelse(account == "32**1245", 40.613811, lat), 
         lon = ifelse(account == "32**1245",-79.188575, lon)) %>% 
  mutate(lat = ifelse(account == "51**3963", 39.953296, lat), 
         lon = ifelse(account == "51**3963", -75.171498, lon)) %>%
  mutate(lat = ifelse(account == "45**7969", 41.073454, lat), 
         lon = ifelse(account == "45**7969", -75.572500, lon)) %>% 
  mutate(lat = ifelse(account == "51**6676", 39.947059,  lat), 
         lon = ifelse(account == "51**6676", -75.164530, lon)) %>% 
  mutate(lat = ifelse(account == "14**8220", 40.834775,   lat), 
         lon = ifelse(account == "14**8220", -77.672479, lon)) %>% 
  mutate(lat = ifelse(account == "21**0740", 40.251462, lat), 
         lon = ifelse(account == "21**0740", -76.913070, lon)) %>% 
  mutate(lat = ifelse(account == "70**2075", 39.990787,  lat), 
         lon = ifelse(account == "70**2075", -75.615866, lon)) %>% 
  mutate(lat = ifelse(account == "19**1669", 41.042079,   lat), 
         lon = ifelse(account == "19**1669", -76.304124, lon)) %>% 
  mutate(lat = ifelse(account == "03**4033", 40.813582,    lat), 
         lon = ifelse(account == "03**4033", -79.550406, lon)) %>% 
  mutate(lat = ifelse(account == "70**8042", 41.656431,  lat),
         lon = ifelse(account == "70**8042", -80.424375, lon)) %>%
    mutate(lat = ifelse(account %in% c("70**8010", "70**8009", "70**8006",
                                       "70**8002", "70**8001"), 39.876255,  lat),
         lon = ifelse(account %in% c("70**8010", "70**8009", "70**8006",
                                       "70**8002", "70**8001"), -75.242962, lon)) %>%
    mutate(lat = ifelse(account == "70**7009", 40.282027,  lat),
         lon = ifelse(account == "70**7009", -79.550674, lon)) %>%
  mutate(lat = ifelse(account == "70**5130", 41.995120,   lat),
         lon = ifelse(account == "70**5130", -77.124804, lon)) %>%
  mutate(lat = ifelse(account %in% c("70**4109", "70**4074"), 40.492822, lat),
         lon = ifelse(account %in% c("70**4109", "70**4074"), -77.114086, lon)) %>%
  mutate(lat = ifelse(account == "70**3200", 40.367604,   lat),
         lon = ifelse(account == "70**3200", -78.430881, lon)) %>%
  mutate(lat = ifelse(trade_name == "S & B ASSOCIATES INC", 39.950269,   lat), 
         lon = ifelse(trade_name == "S & B ASSOCIATES INC",  -75.147441, lon)) %>%
  mutate(lat = ifelse(account == "63**9067", 40.159599, lat),
         lon = ifelse(account == "63**9067", -80.276352, lon)) %>%
  mutate(lat = ifelse(account == "63**9165", 40.367822,  lat),
         lon = ifelse(account == "63**9165", -80.290398, lon)) %>%
  mutate(lat = ifelse(account == "23**0772", 39.9049401,  lat),
         lon = ifelse(account == "23**0772", -75.3885344, lon)) %>%
  mutate(lat = ifelse(account == "40**0587", 41.347524,   lat),
         lon = ifelse(account == "40**0587", -75.960558, lon)) %>%
  mutate(lat = ifelse(account == "62**8390", 41.841740,    lat),
         lon = ifelse(account == "62**8390", -79.354088, lon)) %>%
  mutate(lat = ifelse(account == "02**5373", 40.504864,     lat),
         lon = ifelse(account == "02**5373", -79.675642, lon)) %>%
  mutate(lat = ifelse(account == "54**9250", 40.690829, lat),
         lon = ifelse(account == "54**9250", -76.260003, lon)) %>%
  mutate(lat = ifelse(account == "15**9949", 39.986204, lat),
         lon = ifelse(account == "15**9949", -75.805867, lon)) 

# ADD ADDITIONAL GEOCODING MUTATES HERE

3.7. Write out the dataset

We export our PA baseline data set as a csv.

write.csv(pa_geocoded_fixed, "//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/dataOutputs/pa_geocoded_fixed_5_15_20.csv")

4. PA Data - Socrata Routine for New Data

This is the data routine we will use for updating the PA data. Its naming conventions conform to similar routines for NJ and DE and its data outputs will integrate with those other states.

This routine involves downloading license data from the PA SODA API endpoint, (so long as that is the standard format for such data) and cross-checking it against baseline data to close out old retailer locations and add entries for new ones. Observations without a geolocation in the state database are then geocoded and errors are checked.

4.1. Load new data from Socrata

This routine is basically the same as the baseline routine from Section 3.

Steps:

  1. Grab the data from the PA SODA API. (read.socrata)

  2. Filter to remove out of state and vending observations

  3. Add a time stamp with the date of download, a column for 'state' and coerce the license expiration date to date ('ymd')

  4. Rename relevant columns to lat, lon and address_full

  5. Use mutate_if to coerce all factors to character

  6. Replace "&" characters with "AND" (better for geocoding)

Address data are in a json-like format (in brackets etc) but this should not bother the Google geocoder.

retailers_Socrata_pa <- read.socrata("https://data.pa.gov/resource/ut72-sft8.json") %>%
  filter(county != "UNKNOWN/OUT OF STATE",
         license_type != "Vending") %>%
  mutate(publish_date = today(tzone = "America/New_York"),
         state = "PA",
         expiration_date = ymd(expiration_date)) %>%
  rename(lat = location_1.latitude,
         lon = location_1.longitude,
         address_full = location_1.human_address) %>%
  dplyr::select(-legal_name, -postal_code, -country) %>%
  mutate_if(is.factor, as.character) %>%
  mutate(address_full = str_replace(address_full, "&", "AND"))

4.2. Load most current stored data set

We load up our saved baseline data, from wherever that may be stored. When running this code, the filepath in the following code chunk will likely need to be changed to reflect the most recent PA baseline data set.

Note that we dplyr::select only our canonical_names to account for the fact that when we wrote these data out, they likely were imparted with some kind of column denoting a row id. We don't want that.

We also make sure our data types are correct - using a mutate_if statement to turn factors to characters and a mutate statement to turn our dates into date format

stored_pa <- read.csv("//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/dataOutputs/pa_geocoded_fixed_5_15_20.csv") %>%
  dplyr::select(canonical_names) %>%
  mutate_if(is.factor, as.character) %>%
  mutate(expiration_date = ymd(expiration_date),
         publish_date = ymd(publish_date))

4.3. Join new data and stored data

Check to see which licenses are new in the incoming data. When we find new entries that match entries in our existing data, we update their date of expiry. If data entries in the old database are not represented in the new one, we mark them as expired.

Note the use of the dplyr version of ifelse called if_else here in order to make sure the result is a date and not numeric.

Here's what this does:

  1. Full Join of retailers from baseline PA data (stored_pa) to new data (retailers_Socrata_pa), joined by both account and trade name. Since the data sets have identical names, this produces an "x" and "y" column for each of our canonical names - x being the left side and y being the right side of the join.

  2. If there is no expiration date on file for the new set (e.g. there is no entry for a retailer on file in the newest data set), assume the account is expired - mark it "EXPIRED" or else "ACTIVE"

  3. If it's expired (e.g. there is no corresponding y entry for a given x entry), mark an expiration date as being the last active date of the license on file from the existing database.

  4. Create a new set of canonical columns by checking to see if both x and y columns are filled for each of our variables - if there's no x, put the y data in there and vice versa.

  5. Use similar logic to fill in address, county, publish date and license type data.

  6. Remove the columns which are left over from the join (e.g. lat.x, license_type.y) using dplyr::select

In this join the most recent data set goes second (e.g. the "y") data set.

joined_pa <- full_join(stored_pa, 
                     retailers_Socrata_pa, by = c("account", "trade_name")) %>%
  mutate(expired_y_n = ifelse(is.na(expiration_date.y) == TRUE, "EXPIRED", "ACTIVE"),
         expiration_date = if_else(expired_y_n == 'EXPIRED', expiration_date.x, ymd(expiration_date.y)),
         lat = ifelse(is.na(lat.x) == FALSE, lat.x, lat.y),
         lon = ifelse(is.na(lon.x) == FALSE, lon.x, lon.y),
         address_full = ifelse(is.na(address_full.x) == FALSE, address_full.x, address_full.y),
         county = ifelse(is.na(county.y) == TRUE, county.x, county.y),
         publish_date = if_else(is.na(publish_date.y) == TRUE, publish_date.x, publish_date.y),
         license_type = ifelse(is.na(license_type.y) == TRUE, license_type.x, license_type.y),
         state = "PA") %>%
  dplyr::select(-lat.x, -lon.x, -lat.y, -lon.y, 
         -address_full.x, -address_full.y, -county.x, -county.y,
         -publish_date.x, -publish_date.y, -state.x, -state.y, -license_type.x, -license_type.y,
         -expiration_date.x, -expiration_date.y)

Using the summary function, we can see how much NA data we have for our geolocations.

summary(is.na(joined_pa$lat))

4.4. Geocode missing information

This routine is the same as section 3.4, but is re-capitulated here with the object names relevant to this section.

The google geocoder takes a list of addresses and returns a list of lat/lon pairs.

  1. We create a dataframe of observations to be geocoded (to_geocode_pa) which lack lat/lon data.

  2. We send that data to the geocoder via ggmap (geocode).

  3. We cbind the returned lat/lon pairs to the rest of the columns in the to_geocode_pa dataframe

  4. We rbind the rows from our to_geocode_pa set, which now have x-y info, to our data which did not need to be geocoded in the first place and create a dataframe geocoded_pa.

  5. Perform a data check to see what might still be missing

to_geocode_pa <- joined_pa %>%
  filter(is.na(lat) == TRUE)

geocoded_pa <- geocode(to_geocode_pa$address_full, source =  "google") %>%
  cbind(., to_geocode_pa %>%
          select(-lat, -lon)) %>%
  rbind(joined_pa %>%
          filter(is.na(lat) == FALSE), .)

summary(is.na(geocoded_pa$lat))

4.5. Perform quality check for bad geocodes

When geocodes are wrong, they are often very wrong, and set somewhere wildly different from our target area. We do a spatial join with the PA shapefile, keep only the observations which fall outside of PA. We can check these observations and potentially geocode them manually.

We do this through the following steps:

  1. Perform a spatial join using st_join from the sf package to see which points from pa_geocoded fall outside PA (excluding those we know are NA at present). To do this we need to reproject our lat/lon data as an sf vector object projected in WGS84 (web mercator, crs = 4326) and join it to pa_shp.

  2. We filter those observations which come back as is.na(STUSPS) == TRUE (e.g. they don't join at all to the PA tigris shapefile and don't take on any of it's attributes like STUSPS) and know that these are the ones which fell outside our shapefile.

  3. We dplyr::select only our canonical variables from these and extract the lat/lon from their geometries using a mutate statement. We want to turn this back into a dataframe with lat/lon data and not geometries like an sf object. So we set it as.data.frame and remove the geometry column.

  4. We now rbind these data back to our geocoded_pa observations which lacked lat/lon in the first place (filter(is.na(lat) == FALSE)), to recreate our original geocoded_pa data set with a better knowledge of what lacks good lat/lon data. Those missing or bad geocodes are now all labeled NA.

errors_pa <- st_join(geocoded_pa %>% 
          filter(is.na(lat) == FALSE) %>% 
          st_as_sf(., coords = c("lon", "lat"), crs = 4326), 
        pa_shp, 
        join = st_within, 
        left = TRUE) %>%
  filter(is.na(STUSPS) == TRUE) %>%
  mutate(lon=map_dbl(geometry, ~st_centroid(.x)[[1]]),
         lat=map_dbl(geometry, ~st_centroid(.x)[[2]]))%>%
  dplyr::select(canonical_names) %>%
  as.data.frame() %>%
  dplyr::select(-geometry) %>%
  rbind(., geocoded_pa %>%
          filter(is.na(lat) == TRUE))

4.6. Manually geocode remaining observations

The observations that don't geocode will need to be dealt with manually (e.g. input lat/lon coordinates based on google map research, license reasearch etc.,). Similarly, observations that geocode poorly and end up on our errors_pa list need to be manually geocoded.

This has to be done by hand unfortuately.

We write out errors_pa and then type in the lat/lon manually in Excel. This is much easier than doing a series of mutate statements.

The name of this filepath should change each time you do it so as not to overwrite previous work. As you are geocoding the observations by hand, keep the file in csv form, rather than xlsx.

write.csv(errors_pa, "filepath goes here.csv")

Once we've done our geocoding research, and add in the lat/lon data which was missing, we load the (now correct) data back in using a read.csv command and join it to our properly geocoded data. Remember that you need to change the file path from the one in the code chunk below.

We use an anti_join to geocoded to isloate those observations from geocoded_pa that didn't end up our errors_pa data set, and then bind the rows together with the manually corrected errors. Note that inside the rbind function we do some data cleaning with our uploaded errors so that we keep only the canonical names and our column datatypes are in the correct formats.

Note that inside the rbind function we do some data cleaning with our uploaded errors so that we keep only the canonical names and our column datatypes are in the correct formats. There is an important hitch here - when the data were exported to Excel for manual cleaning, they may have had their dates scrambled to a month/day/year format rather than the year/month/day our data use. If an error shows up saying "All formats failed to parse. No formats found." this means that one must adjust the ymd function (which turns a string into a date format) to reflect the correct order (e.g. mdy for month/day/year).

geocoded_pa_fixed <-  geocoded_pa %>% 
  anti_join(., errors_pa) %>%
  rbind(., read.csv("input filepath.csv") %>%
  dplyr::select(canonical_names) %>%
  mutate_if(is.factor, as.character) %>%
  mutate(expiration_date = mdy(expiration_date),
         publish_date = mdy(publish_date)))

4.7. Write out the dataset

This is our final PA data set - geocoded_pa_fixed. It can be written out to file. We will bind it to our DE and NJ data later in the code base.

write.csv(geocoded_pa_fixed, "filepath goes here.csv")

5. Precursor Routine - Baseline NJ Data

The New Jersey data routine is significantly different from the PA and DE routines which use SODA API calls and relatively standardized data. We have NJ data from March of 2018 and August of 2019. The latter data set is thought to be relatively complete. However, New Jersey has not geocoded its data, so this baseline routine both compares the 2018 and 2019 data and integrates them, but it geocodes our baseline data set.

5.1. Read March, 2018 Data

  1. We read the 2018 info from excel (read_excel).

  2. We pull a five digit zip code out from a nine digit code used in their data set. (see: Zip).

  3. We create an address_full field for geocoding and replace "&" characters with "AND".

  4. We add columns or change names to correspond to our "canonical" column names.

NJ_3_2018 <- read_excel("//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/inputData/RetailerMaps/RetailerMaps/NJ (ncooper@asc.upenn.edu)/Carreras Tartak J W130294 Provided W130294 - Cigarette retailers.xlsx", skip = 5) %>%
  mutate(Zip = ifelse(str_length(Zip) == 9, str_sub(Zip, end = -5), Zip))%>%
  unite(., "address_full", sep = ", ",
        c("Street", "City", "State", "Zip")) %>%
  mutate(address_full = str_replace(address_full, "&", "AND"))%>%
  mutate(expired_y_n = "ACTIVE", 
         expiration_date = ymd("2019-03-31"),  # is this correct?
         publish_date = ymd("2018-04-01"),
         account = str_c("NJ_18", row_number(), sep = "_"), 
         license_type = NA, county = NA,
         state = "NJ",
         Trade = ifelse(is.na(Trade) == TRUE, Name, Trade)) %>% 
    rename(trade_name = Trade) %>% 
    dplyr::select(-Loc_Code, -Name)

5.2. Geocode Data

We do a preliminary geocode - recall, none of the addresses have lat/lon information, so this procedure is different than the preceding PA sections. We are geocoding all of the data, and we expect to have lots of errors and ambiguities to sort out.

  1. We send a vector of addresses to the geocoder via ggmap (geocode).

  2. We cbind the returned lat/lon pairs to the rest of the columns in the NJ_3_2018 dataframe, creating a new dataframe NJ_3_2018_geocoded

  3. We then check the data to see how many observations failed (summary)

NJ_3_2018_geocoded <- geocode(NJ_3_2018$address_full, source =  "google") %>%
  cbind(., NJ_3_2018)

summary(is.na(NJ_3_2018_geocoded$lat))

5.3. Load August, 2019 Data

Load the second data set using the same routine as in section 5.1

There are almost twice as many observations as our 2018 data. We have come to learn that this is because 2018 is an incomplete data set.

NJ_8_2019 <- read_excel("//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/inputData/RetailerMaps/RetailerMaps/NJ (ncooper@asc.upenn.edu)/Carreras-Tartak J C148764 Cigarette retailers.xlsx", skip = 5)  %>%
  mutate(Zip = ifelse(str_length(Zip) == 9, str_sub(Zip, end = -5), Zip))%>%
  unite(., "address_full", sep = ", ",
        c("Street", "City", "State", "Zip")) %>%
  mutate(address_full = str_replace(address_full, "&", "AND"))%>%
  mutate(expired_y_n = "ACTIVE", 
         expiration_date = ymd("2020-03-31"),
         publish_date = ymd("2019-04-01"),
         account = str_c("NJ_19", row_number(), sep = "_"), 
         license_type = NA, county = NA,
         state = "NJ",
         Trade = ifelse(is.na(Trade) == TRUE, Name, Trade)) %>% 
    rename(trade_name = Trade) %>% 
    dplyr::select(-Loc_Code, -Name)

5.4. Coalesce Data Sets

Join the two data sets, see what's new. If there is a new and old record, assign ID as the oldest of the two account numbers, change the expiration date.

In this join the most recent data set goes second (e.g. the "y") data set.

NJ_join <- full_join(NJ_3_2018_geocoded, NJ_8_2019, by = "address_full") %>%
mutate(expired_y_n = ifelse(is.na(expiration_date.y) == TRUE, "EXPIRED", "ACTIVE"),
         expiration_date = if_else(expired_y_n == 'EXPIRED', expiration_date.x, ymd(expiration_date.y)),
         county = ifelse(is.na(county.y) == TRUE, county.x, county.y),
         publish_date = if_else(is.na(publish_date.y) == TRUE, publish_date.x, publish_date.y),
         license_type = ifelse(is.na(license_type.y) == TRUE, license_type.x, license_type.y),
         account = ifelse(is.na(account.x) == FALSE, account.x, account.y),
         trade_name = ifelse(is.na(trade_name.y) == FALSE, trade_name.y, trade_name.x),
         expiration_date = if_else(is.na(expiration_date.y) == FALSE, expiration_date.y, expiration_date.x),
         expiration_date = if_else(is.na(expiration_date.y) == FALSE, expiration_date.y, expiration_date.x),
         publish_date = if_else(is.na(publish_date.x) == FALSE, publish_date.x, publish_date.y),
         state = "NJ",
         county = NA) %>%
  select(-state.x, - state.y, -county.y, -county.x, -account.x, -account.y, -trade_name.y, -trade_name.x,
         -expired_y_n.x, -expired_y_n.y, -expiration_date.y, -expiration_date.x, -license_type.x, -license_type.y,
         -publish_date.x, -publish_date.y)

We can check our data to see how many unique addresses we have.

NJ_join %>% 
  dplyr::select(address_full) %>% 
  unique() %>% 
  nrow()

NJ_join %>%
  dplyr::select(address_full, trade_name)%>% 
  unique() %>% 
  nrow()

5.5. Geocode missing observations

Now we undertake a second round of geocoding to add lat/lon information to our data set - our newest observations don't have any lat/lon information.

This routine is the same as section 3.4, but is re-capitulated here with the object names relevant to this section.

The google geocoder takes a list of addresses and returns a list of lat/lon pairs.

  1. We create a dataframe of observations to be geocoded (to_geocode_nj) which lack lat/lon data.

  2. We send that data to the geocoder via ggmap (geocode).

  3. We cbind the returned lat/lon pairs to the rest of the columns in the to_geocode_nj dataframe

  4. We rbind the rows from our to_geocode_nj set, which now have x-y info, to our data which did not need to be geocoded in the first place and create a dataframe geocoded_nj.

  5. Perform a data check to see what might still be missing (summary)

to_geocode_nj <- NJ_join %>%
  filter(is.na(lat) == TRUE)

geocoded_nj <- geocode(to_geocode_nj$address_full, source =  "google") %>%
  cbind(., to_geocode_nj %>%
          select(-lat, -lon)) %>%
  rbind(NJ_join %>%
          filter(is.na(lat) == FALSE), .)

summary(is.na(geocoded_nj$lat))

5.6. Perform data quality check for bad geocodes

This section is roughly the same as 3.5 - the methodology is reprinted here using the appropriate object names

When geocodes are wrong, they are often very wrong, and set somewhere wildly different from our target area. We do a spatial join with the NJ shapefile, keep only the observations which fall outside of NJ. We can check these observations and potentially geocode them manually.

We do this through the following steps:

  1. Perform a spatial join using st_join from the sf package to see which points from geocoded_nj fall outside PA (excluding those we know are NA at present). To do this we need to reproject our lat/lon data as an sf vector object projected in WGS84 (web mercator, crs = 4326) and join it to nj_shp.

  2. We filter those observations which come back as is.na(STUSPS) == TRUE (e.g. they don't join at all to the NJ tigris shapefile and don't take on any of it's attributes like STUSPS) and know that these are the ones which fell outside our shapefile.

  3. We dplyr::select only our canonical variables from these and extract the lat/lon from their geometries using a mutate statement. We want to turn this back into a dataframe with lat/lon data and not geometries like an sf object. So we set it as.data.frame and remove the geometry column.

  4. We now rbind these data back to our geocoded_nj observations which lacked lat/lon in the first place, to create a data set consisting of all our observations which we presume have bad geocodes.

errors_nj <- st_join(geocoded_nj %>% 
          filter(is.na(lat) == FALSE) %>% 
          st_as_sf(., coords = c("lon", "lat"), crs = 4326), 
        nj_shp, 
        join = st_within, 
        left = TRUE) %>%
  filter(is.na(STUSPS) == TRUE) %>%
  mutate(lon=map_dbl(geometry, ~st_centroid(.x)[[1]]),
         lat=map_dbl(geometry, ~st_centroid(.x)[[2]]))%>%
  dplyr::select(canonical_names) %>%
  as.data.frame() %>%
  dplyr::select(-geometry) %>%
  rbind(., geocoded_nj %>%
          filter(is.na(lat) == TRUE))

We perform a data check to see how many NA values we still have in our data.

summary(is.na(errors_nj$lat))

nrow(errors_nj)

5.7. Manually geocode missing observations

This routine is similar to Section 4.6 but is re-written here with appropriate object names

The observations that don't geocode will need to be dealt with manually (e.g. input lat/lon coordinates based on google map research, license reasearch etc.,). Similarly, observations that geocode poorly and end up on our errors_nj list need to be manually geocoded.

This has to be done by hand unfortuately.

We write out errors_nj and then type in the lat/lon manually in Excel. This is much easier than doing a series of mutate statements.

The name of this filepath should change each time you do it so as not to overwrite previous work. As you are geocoding the observations by hand, keep the file in csv form, rather than xlsx.

write.csv(errors_nj, "//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/dataOutpurs/errors_nj_6_9_20.csv")

Once we've done our geocoding research, and add in the lat/lon data which was missing, we load the (now correct) data back in using a read.csv command and join it to our properly geocoded data.

We use an anti_join to geocoded to isloate those observations from geocoded_nj that didn't end up our errors_nj data set, and then bind the rows together with the manually corrected errors.

Note that inside the rbind function we do some data cleaning with our uploaded errors so that we keep only the canonical names and our column datatypes are in the correct formats. There is an important hitch here - when the data were exported to Excel for manual cleaning, they may have had their dates scrambled to a month/day/year format rather than the year/month/day our data use. If an error shows up saying "All formats failed to parse. No formats found." this means that one must adjust the ymd function (which turns a string into a date format) to reflect the correct order (e.g. mdy for month/day/year).

We also add a series of mutate statements designed to catch erroneous publish/expiration dates imputed into an old data routine (these data were the ones being used for manual geocoding). This is essentially a series of replacements for old imputed values using ifelse statements to catch and over-write incorrect dates.

Nora Johnston did this manually based on an export of errors_nj, and there are a handful of corrections which I did later where I use mutate commandes to alter the lat/lon info.

geocoded_nj_fixed <-  geocoded_nj %>% 
  anti_join(., errors_nj, by = "account") %>%
  rbind(., read.csv("//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/inputData/RetailerMaps/RetailerMaps/repairedGeocodes/errors_nj_6_9_20_mf_edits.csv") %>%
  dplyr::select(canonical_names) %>%
  mutate_if(is.factor, as.character) %>%
  mutate(expiration_date = mdy(expiration_date),
         publish_date = mdy(publish_date))) %>%
  mutate(publish_date = as.character(publish_date), 
         expiration_date = as.character(expiration_date), 
         publish_date = ifelse(publish_date == '2018-03-08', '2018-04-01', publish_date), 
         publish_date = ifelse(publish_date == '2019-08-01', '2019-04-01', publish_date), 
         expiration_date = ifelse(expiration_date == '2019-03-07','2019-03-31', expiration_date), 
         expiration_date = ifelse(expiration_date == '2020-07-31', '2020-03-31', expiration_date), 
         publish_date = ymd(publish_date), 
         expiration_date = ymd(expiration_date)) %>%
  mutate(lat = ifelse(account == "NJ_19_1794", 40.379592, lat),
         lon = ifelse(account == "NJ_19_1794", -74.580245, lon)) %>%
  mutate(lat = ifelse(account == "NJ_19_797", 40.864672,   lat),
         lon = ifelse(account == "NJ_19_797", -73.961253, lon)) %>%
  mutate(lat = ifelse(account == "NJ_18_2339", 40.741128,   lat),
         lon = ifelse(account == "NJ_18_2339", -74.535828, lon))

5.8. Write out the data

This is our final NJ data set - geocoded_nj_fixed. It can be written out to file. We will bind it to our DE and PA data later in the code base.

write.csv(geocoded_nj_fixed, "//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/dataOutputs/nj_geocoded_7_20_20.csv")

6. Routine for New NJ Data (Expected August, 2020)

Presumably, New Jersey will be releasing new data in August, 2020. Because their data sets have been somewhat non-standard to this point, what follows is a possible workflow to intake and clean the new NJ data, presuming the standards are exactly the same of last year.

6.1. Load Baseline data

This is our standard loading procedure, which keeps only the relevant column names and makes sure dates and character formats are correct. If this creates an error, the data may have been disturbed.

stored_nj <- read.csv("filepath goes here") %>%
  dplyr::select(canonical_names) %>%
  mutate_if(is.factor, as.character) %>%
  mutate(expiration_date = ymd(expiration_date),
         publish_date = ymd(publish_date))

6.2. Load New Data

We load new data presuming same data specifications as August 2019 data (same columns and data types.).

This is a spot where there is potential trouble.

One will need to specify the filepath and in the event these excel data are not formatted the same as last year, alter the skip command in the read_excel function. skip specifies how many rows to skip over before finding the column headers. Look over the data set in excel to see how it looks.

If column names are different from the assumed format, using rename commands in this function or changing column names in the excel sheet should solve the problem.

retailers_new_NJ <- read_excel("//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/inputData/RetailerMaps/RetailerMaps/NJ (ncooper@asc.upenn.edu)/NJ Cigarette Retailers 2020 (OPRA C162332) (1).xlsx", skip = 5)  %>%
  mutate(Zip = ifelse(str_length(Zip) == 9, str_sub(Zip, end = -5), Zip))%>%
  unite(., "address_full", sep = ", ",
        c("Street", "City", "State", "Zip")) %>%
  mutate(address_full = str_replace(address_full, "&", "AND"))%>%
  mutate(expired_y_n = "ACTIVE", 
         expiration_date = ymd("2021-03-31"),  # is this correct?
         publish_date = ymd("2020-04-01"),
         account = str_c("NJ_20", row_number(), sep = "_"), 
         license_type = NA, county = NA,
         state = "NJ",
         Trade = ifelse(is.na(Trade) == TRUE, Name, Trade)) %>% 
    rename(trade_name = Trade) %>% 
    dplyr::select(-Loc_Code, -Name)

6.3. Coalesce Data Sets

Check to see which licenses are new. If they are old, we can throw them out and when we add the new data, we will just update their date of expiry. If data in the old database are not represented in the new one, we keep them in, but mark them as expired.

Note the use of the dplyr version of ifelse called if_else here in order to make sure the result is a date and not numeric.

Here's what this does:

  1. Full Join of retailers from baseline NJ data (stored_nj) to new data (retailers_new_NJ), joined by both account and trade name. Since the data sets have identical names, this produces an "x" and "y" column for each of our canonical names - x being the left side and y being the right side of the join.

  2. If there is no expiration date on file for the new set (e.g. there is no entry for a retailer on file in the newest data set), assume the account is expired - mark it "EXPIRED" or else "ACTIVE"

  3. If it's expired (e.g. there is no corresponding y entry for a given x entry), mark an expiration date as being the last active date of the license on file from the existing database.

  4. Create a new set of canonical columns by checking to see if both x and y columns are filled for each of our variables - if there's no x, put the y data in there and vice versa.

  5. Use similar logic to fill in address, county, publish date and license type data.

  6. Remove the columns which are left over from the join (e.g. lat.x, license_type.y) using dplyr::select

In this join the most recent data set goes second (e.g. the "y") data set.

NJ_join <- full_join(stored_nj, retailers_new_NJ, by = "address_full") %>%
mutate(expired_y_n = ifelse(is.na(expiration_date.y) == TRUE, "EXPIRED", "ACTIVE"),
         expiration_date = if_else(expired_y_n == 'EXPIRED', expiration_date.x, ymd(expiration_date.y)),
         county = ifelse(is.na(county.y) == TRUE, county.x, county.y),
         publish_date = if_else(is.na(publish_date.y) == TRUE, publish_date.x, publish_date.y),
         license_type = ifelse(is.na(license_type.y) == TRUE, license_type.x, license_type.y),
         account = ifelse(is.na(account.x) == FALSE, account.x, account.y),
         trade_name = ifelse(is.na(trade_name.y) == FALSE, trade_name.y, trade_name.x),
         expiration_date = if_else(is.na(expiration_date.y) == FALSE, expiration_date.y, expiration_date.x),
         expiration_date = if_else(is.na(expiration_date.y) == FALSE, expiration_date.y, expiration_date.x),
         publish_date = if_else(is.na(publish_date.x) == FALSE, publish_date.x, publish_date.y),
         state = "NJ",
         county = NA) %>%
  select(-state.x, - state.y, -county.y, -county.x, -account.x, -account.y, -trade_name.y, -trade_name.x,
         -expired_y_n.x, -expired_y_n.y, -expiration_date.y, -expiration_date.x, -license_type.x, -license_type.y,
         -publish_date.x, -publish_date.y) %>%
  dplyr::select(canonical_names)

6.4. Geocode missing observations

Now we geocode the observations which lack a lat/lon (to_geocode_nj) by sending addresses to the Google geocoding engine, fetching a lat/lon pair (geocode) and then binding the lat/lon columns back to the rest of the data (cbind) and binding the geocoded observations back to those data which had a lat/lon value (rbind).

The summary function will tell us how many geocodes have been unsuccessful up until this point.

to_geocode_nj <- NJ_join %>%
  filter(is.na(lat) == TRUE)

geocoded_nj <- geocode(to_geocode_nj$address_full, source =  "google") %>%
  cbind(., to_geocode_nj %>%
          select(-lat, -lon)) %>%
  rbind(NJ_join %>%
          filter(is.na(lat) == FALSE), .)

summary(is.na(geocoded_nj$lat))

6.5. Perform data quality check for bad geocodes

This section is roughly the same as 3.5 - the methodology is reprinted here using the appropriate object names

When geocodes are wrong, they are often very wrong, and set somewhere wildly different from our target area. We do a spatial join with the NJ shapefile, keep only the observations which fall outside of NJ. We can check these observations and potentially geocode them manually.

We do this through the following steps:

  1. Perform a spatial join using st_join from the sf package to see which points from geocoded_nj fall outside NJ (excluding those we know are NA at present). To do this we need to reproject our lat/lon data as an sf vector object projected in WGS84 (web mercator, crs = 4326) and join it to nj_shp.

  2. We filter those observations which come back as is.na(STUSPS) == TRUE (e.g. they don't join at all to the NJ tigris shapefile and don't take on any of it's attributes like STUSPS) and know that these are the ones which fell outside our shapefile.

  3. We dplyr::select only our canonical variables from these and extract the lat/lon from their geometries using a mutate statement. We want to turn this back into a dataframe with lat/lon data and not geometries like an sf object. So we set it as.data.frame and remove the geometry column.

  4. We now rbind these data back to our geocoded_nj observations which lacked lat/lon in the first place, to create a data set consisting of all our observations which we presume have bad geocodes.

errors_nj <- st_join(geocoded_nj %>% 
          filter(is.na(lat) == FALSE) %>% 
          st_as_sf(., coords = c("lon", "lat"), crs = 4326), 
        nj_shp, 
        join = st_within, 
        left = TRUE) %>%
  filter(is.na(STUSPS) == TRUE) %>%
  mutate(lon=map_dbl(geometry, ~st_centroid(.x)[[1]]),
         lat=map_dbl(geometry, ~st_centroid(.x)[[2]]))%>%
  dplyr::select(canonical_names) %>%
  as.data.frame() %>%
  dplyr::select(-geometry) %>%
  rbind(., geocoded_nj %>%
          filter(is.na(lat) == TRUE)%>%
          dplyr::select(canonical_names))

We perform a data check to see how many NA observations remain in our data.

summary(is.na(errors_nj$lat))

nrow(errors_nj)

6.6. Manually geocode missing observations

This routine is similar to Section 4.6 but is re-written here with appropriate object names

The observations that don't geocode will need to be dealt with manually (e.g. input lat/lon coordinates based on google map research, license reasearch etc.,). Similarly, observations that geocode poorly and end up on our errors_nj list need to be manually geocoded.

This has to be done by hand unfortuately.

We write out errors_nj and then type in the lat/lon manually in Excel. This is much easier than doing a series of mutate statements.

The name of this filepath should change each time you do it so as not to overwrite previous work. As you are geocoding the observations by hand, keep the file in csv form, rather than xlsx.

write.csv(errors_nj, "filepath goes here")

Once we've done our geocoding research, and add in the lat/lon data which was missing, we load the (now correct) data back in using a read.csv command and join it to our properly geocoded data.

We use an anti_join to geocoded to isloate those observations from geocoded_nj that didn't end up our errors_nj data set, and then bind the rows together with the manually corrected errors. Note that inside the rbind function we do some data cleaning with our uploaded errors so that we keep only the canonical names and our column datatypes are in the correct formats.

geocoded_nj_fixed <-  geocoded_nj %>% 
  anti_join(., errors_nj) %>%
  rbind(., read.csv("FILEPATH FOR HAND-GEOCODED OBSERVATIONS") %>%
  dplyr::select(canonical_names) %>%
  mutate_if(is.factor, as.character) %>%
  mutate(expiration_date = ymd(expiration_date),
         publish_date = ymd(publish_date)))

6.7. Write out the data

This is our final NJ data set - geocoded_nj_fixed. It can be written out to file. We will bind it to our DE and PA data later in the code base.

write.csv(geocoded_nj_fixed, "filepath goes here")

7. Precursor Routine - Baseline Delaware data

This precursor routine was used to load and clean the first set of Delaware data we collected - in March of 2020. This is a precursor to our code routine (e.g. our code takes in NEW data). The following steps do not need to be re-run, and act only as documentation for the building of the portion of our retailer database.

Comes from here: https://data.delaware.gov/Licenses-and-Certifications/Delaware-Business-Licenses/5zy2-grhr

There are many license types in these data, and those tobacco related include wholesalers and internet retailers

This will need to be filtered (by category) on the instructions of the team.

We should also filter out wholsalers - many of these are outside delaware - I suppose need to be registered there.

7.1. Load Data From Socrata

Data from March, 2020

This routine is basically the same as the baseline routine from PA, which uses the same general data standards, but it applies to a broad set of business licenses that are published as a group.

Steps:

  1. Grab the data from the DE SODA API. (read.socrata)

  2. Filter to keep only data of the categories TOBACCO PRODUCTS RETAILER and RETAILER-TOBACCO

  3. Add a time stamp with the date of download, a column for 'state' and coerce the license expiration date to date ('ymd')

  4. Rename relevant columns to lat, lon, account and license_type

  5. unite address fields into one address column for geocoding

  6. Remove un-needed variables using select

  7. Use mutate_if to coerce all factors to character

  8. Replace "&" characters with "AND" (better for geocoding)

retailers_Socrata_de_pre <- read.socrata("https://data.delaware.gov/resource/5zy2-grhr.json") %>%
  filter(str_detect(category, "TOBACCO PRODUCTS RETAILER") | str_detect(category, "RETAILER-TOBACCO")) %>%
  mutate(publish_date = ymd(current_license_valid_from),
         expiration_date = ymd(current_license_valid_to),
         expired_y_n = "ACTIVE",
         trade_name = business_name,
         county = NA,
         state = "DE") %>%
  rename(lat = geocoded_location.latitude,
         lon = geocoded_location.longitude,
         account = license_number,
         license_type = category) %>%
  mutate(zip = ifelse(str_length(zip) == 9, str_sub(zip, end = -5), zip)) %>%
  unite(., "address_full", sep = " ",
        c("address_2", "city", "state", "zip")) %>%
  mutate(state = "DE") %>%
  dplyr::select(-business_name, -current_license_valid_from, 
                -current_license_valid_to, -country, -geocoded_location.human_address,
                -address_1) %>%
  mutate_if(is.factor, as.character) %>%
  mutate(address_full = str_replace(address_full, "&", "AND"))

Use the summary command to explore NA data

summary(is.na(retailers_Socrata_de_pre$lat))

7.2. Geocode data

Now we geocode the observations which lack a lat/lon (to_geocode_de_pre) by sending addresses to the Google geocoding engine, fetching a lat/lon pair (geocode) and then binding the lat/lon columns back to the rest of the data (cbind) and binding the geocoded observations back to those data which had a lat/lon value (rbind).

The summary function will tell us how many geocodes have been unsuccessful up until this point.

to_geocode_de_pre <- retailers_Socrata_de_pre %>%
  filter(is.na(lat) == TRUE)

de_geocoded_pre <- geocode(to_geocode_de_pre$address_full, source =  "google") %>%
  cbind(., to_geocode_de %>%
          dplyr::select(-lat, -lon)) %>%
  rbind(retailers_Socrata_de_pre %>%
          filter(is.na(lat) == FALSE), .)

summary(is.na(de_geocoded_pre$lat))

7.3. Perform data quality check for bad geocodes

One strange issue with DE data is that there are observations in there that are absolutely outside Delaware - the licenses themselves say so. We clean these out later, but they obviously trip up the following routine.

When geocodes are wrong, they are often very wrong, and set somewhere wildly different from our target area. We do a spatial join with the DE shapefile, keep only the observations which fall outside of DE. We can check these observations and potentially geocode them manually.

We do this through the following steps:

  1. Perform a spatial join using st_join from the sf package to see which points from de_geocoded_pre fall outside DE (excluding those we know are NA at present). To do this we need to reproject our lat/lon data as an sf vector object projected in WGS84 (web mercator, crs = 4326) and join it to de_shp.

  2. We filter those observations which come back as is.na(STUSPS) == TRUE (e.g. they don't join at all to the DE tigris shapefile and don't take on any of it's attributes like STUSPS) and know that these are the ones which fell outside our shapefile.

  3. We dplyr::select only our canonical variables from these and extract the lat/lon from their geometries using a mutate statement. We want to turn this back into a dataframe with lat/lon data and not geometries like an sf object. So we set it as.data.frame and remove the geometry column.

  4. We now rbind these data back to our de_geocoded_pre observations which lacked lat/lon in the first place, to create a data set consisting of all our observations which we presume have bad geocodes.

there are LOTS of addresses which are legitimately outside delaware (check the zip codes, they are in other states)

errors_de_pre <- st_join(de_geocoded_pre %>% 
          filter(is.na(lat) == FALSE) %>% 
          st_as_sf(., coords = c("lon", "lat"), crs = 4326), 
        de_shp, 
        join = st_within, 
        left = TRUE) %>%
  filter(is.na(STUSPS) == TRUE) %>%
  mutate(lon=map_dbl(geometry, ~st_centroid(.x)[[1]]),
         lat=map_dbl(geometry, ~st_centroid(.x)[[2]]))%>%
  dplyr::select(canonical_names) %>%
  as.data.frame() %>%
  dplyr::select(-geometry) %>%
  rbind(., de_geocoded_pre %>%
          filter(is.na(lat) == TRUE))

We check our data to see how many NA values came back.

summary(is.na(errors_de_pre$lat))

7.4. Manually geocode remaining observations

The observations that don't geocode will need to be dealt with manually (e.g. input lat/lon coordinates based on google map research). Similarly, observations that geocode poorly and end up on our errors_de_pre list need to be manually geocoded.

This has to be done by hand unfortuately. You can go through the observations which either have NA values for lat/lon in de_geocoded_pre or are found to be outside DE in the errors_de_pre dataframe. Here we do this with web research and mutate statements where we replace the lat/lon values for individual licenses.

In other data sets and later in this code we export the data to a csv and an RA does the coding, which we then re-import and integrate. The export approach is probably less prone to error from transcription problems.

There are ABSOLUTELY going to be licenses from outside Delaware on this list. You have to check the zip codes.

Note that in our last step, we remove any obsrvations that are outside the state with a filter command.

de_geocoded_pre_fixed <- de_geocoded_pre %>%
  mutate(lat = ifelse(account == 2004200459, 39.563789,  lat),
         lon = ifelse(account == 2004200459, -75.653397, lon))  %>%
  mutate(lat = ifelse(account == 2000107842, 38.925108,   lat),
         lon = ifelse(account == 2000107842, -75.567057, lon)) %>%
  mutate(lat = ifelse(account == 2016604575, 39.649600,   lat),
         lon = ifelse(account == 2016604575, -75.621843 , lon)) %>%
  mutate(lat = ifelse(account == 2007220039, 39.739074,   lat),
         lon = ifelse(account == 2007220039, -75.623293 , lon)) %>%
  mutate(lat = ifelse(account == 1989005158, 39.823859,   lat),
         lon = ifelse(account == 1989005158, -75.499926 , lon)) %>%
  mutate(lat = ifelse(account == 2019603108, 38.455972,   lat),
         lon = ifelse(account == 2019603108, -75.559722 , lon)) %>%
  filter(!account %in% c(2016605565, 2012101218, 2010102260, 1998204762, 2019100273,
                         1989023113, 2020701642, 2020701644, 2018602428, 2018600730,
                         2016100150, 1996105149))

7.5. Write out the dataset

We export our DE baseline data set as a csv.

write.csv(de_geocoded_pre_fixed, "//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/dataOutputs/de_geocoded_pre_6_8_20.csv")

8. DE Data - Socrata Routine for New Data

This is the data routine we will use for updating the DE data. Its naming conventions conform to similar routines for NJ and PA and its data outputs will integrate with those other states.

This routine involves downloading license data from the DE SODA API endpoint, (so long as that is the standard format for such data) and cross-checking it against baseline data to close out old retailer locations and add entries for new ones. Observations without a geolocation in the state database are then geocoded and errors are checked.

8.1. Load new data from Socrata

This routine is basically the same as the baseline routine from PA, which uses the same general data standards, but it applies to a broad set of business licenses that are published as a group.

Steps:

  1. Grab the data from the DE SODA API. (read.socrata)

  2. Filter to keep only data of the categories TOBACCO PRODUCTS RETAILER and RETAILER-TOBACCO

  3. Add a time stamp with the date of download, a column for 'state' and coerce the license expiration date to date ('ymd')

  4. Rename relevant columns to lat, lon, account and license_type

  5. Filter just for observations with zip codes between 197001 and 19980 (Delaware Zip codes) - this avoids problems we had creating baseline data with odd out-of-state points

  6. unite address fields into one address column for geocoding

  7. Remove un-needed variables using select

  8. Use mutate_if to coerce all factors to character

  9. Replace "&" characters with "AND" (better for geocoding)

retailers_Socrata_de <- read.socrata("https://data.delaware.gov/resource/5zy2-grhr.json") %>%
  filter(str_detect(category, "TOBACCO PRODUCTS RETAILER") | str_detect(category, "RETAILER-TOBACCO")) %>%
  mutate(publish_date = ymd(current_license_valid_from),
         expiration_date = ymd(current_license_valid_to),
         expired_y_n = "ACTIVE",
         trade_name = business_name,
         county = NA,
         state = "DE") %>%
  rename(lat = geocoded_location.latitude,
         lon = geocoded_location.longitude,
         account = license_number,
         license_type = category) %>%
  mutate(zip = ifelse(str_length(zip) == 9, str_sub(zip, end = -5), zip)) %>%
  filter(zip >= 19701 & zip <= 19980) %>%
  unite(., "address_full", sep = " ",
        c("address_2", "city", "state", "zip")) %>%
  mutate(state = "DE") %>%
  dplyr::select(-business_name, -current_license_valid_from, 
                -current_license_valid_to, -country, -geocoded_location.human_address,
                -address_1) %>%
  mutate_if(is.factor, as.character) %>%
  mutate(address_full = str_replace(address_full, "&", "AND"))

8.2. Load most current stored data set

We load up our saved baseline data, from wherever that may be stored. When running this code, the filepath in the following code chunk will likely need to be changed to reflect the most recent DE baseline data set.

Note that we dplyr::select only our canonical_names to account for the fact that when we wrote these data out, they likely were imparted with some kind of column denoting a row id. We don't want that.

We also make sure our data types are correct - using a mutate_if statement to turn factors to characters and a mutate statement to turn our dates into date format

stored_de <- read.csv("//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/dataOutputs/de_geocoded_pre_6_8_20.csv") %>%
  dplyr::select(canonical_names) %>%
  mutate_if(is.factor, as.character) %>%
  mutate(account = as.character(account)) %>%
  mutate(expiration_date = ymd(expiration_date),
         publish_date = ymd(publish_date))

8.3. Join new data and stored data

Check to see which licenses are new in the incoming data. When we find new entries that match entries in our existing data, we update their date of expiry. If data entries in the old database are not represented in the new one, we mark them as expired.

Note the use of the dplyr version of ifelse called if_else here in order to make sure the result is a date and not numeric.

Here's what this does:

  1. Full Join of retailers from baseline DE data (stored_de) to new data (retailers_Socrata_de), joined by both account and trade name. Since the data sets have identical names, this produces an "x" and "y" column for each of our canonical names - x being the left side and y being the right side of the join.

  2. If there is no expiration date on file for the new set (e.g. there is no entry for a retailer on file in the newest data set), assume the account is expired - mark it "EXPIRED" or else "ACTIVE"

  3. If it's expired (e.g. there is no corresponding y entry for a given x entry), mark an expiration date as being the last active date of the license on file from the existing database.

  4. Create a new set of canonical columns by checking to see if both x and y columns are filled for each of our variables - if there's no x, put the y data in there and vice versa.

  5. Use similar logic to fill in address, county, publish date and license type data.

  6. Remove the columns which are left over from the join (e.g. lat.x, license_type.y) using dplyr::select

In this join the most recent data set goes second (e.g. the "y") data set.

joined_de <- full_join(stored_de, 
                     retailers_Socrata_de, by = c("account", "trade_name")) %>%
  mutate(expired_y_n = ifelse(is.na(expiration_date.y) == TRUE, "EXPIRED", "ACTIVE"),
         expiration_date = if_else(expired_y_n == 'EXPIRED', expiration_date.x, ymd(expiration_date.y)),
         lat = ifelse(is.na(lat.x) == FALSE, lat.x, lat.y),
         lon = ifelse(is.na(lon.x) == FALSE, lon.x, lon.y),
         address_full = ifelse(is.na(address_full.x) == FALSE, address_full.x, address_full.y),
         county = ifelse(is.na(county.y) == TRUE, county.x, county.y),
         publish_date = if_else(is.na(publish_date.y) == TRUE, publish_date.x, publish_date.y),
         license_type = ifelse(is.na(license_type.y) == TRUE, license_type.x, license_type.y),
         state = "DE") %>%
  dplyr::select(-lat.x, -lon.x, -lat.y, -lon.y, 
         -address_full.x, -address_full.y, -county.x, -county.y, -expired_y_n.x, -expired_y_n.y,
         -publish_date.x, -publish_date.y, -state.x, -state.y, -license_type.x, -license_type.y,
         -expiration_date.x, -expiration_date.y)

A summary call determines if there are any bad geocodes.

summary(is.na(joined_de$lat))

If there are NO TRUE values to this statement, the following step can be skipped (although you will need to rename joined_de as geocoded_de to get step 8.5 to work correctly).

8.4. Geocode missing information

Now we geocode the observations which lack a lat/lon (to_geocode_de) by sending addresses to the Google geocoding engine, fetching a lat/lon pair (geocode) and then binding the lat/lon columns back to the rest of the data (cbind) and binding the geocoded observations back to those data which had a lat/lon value (rbind).

The summary function will tell us how many geocodes have been unsuccessful up until this point.

to_geocode_de <- joined_de %>%
  filter(is.na(lat) == TRUE)

geocoded_de <- geocode(to_geocode_de$address_full, source =  "google") %>%
  cbind(., to_geocode_de %>%
          select(-lat, -lon)) %>%
  rbind(joined_de %>%
          filter(is.na(lat) == FALSE), .)

summary(is.na(geocoded_de$lat))

If this step gives you an error reading Error in if (location == "") return(return_failed_geocode(output)) : argument is of length zero, run the following code:

geocoded_de <- joined_de

8.5. Perform quality check for bad geocodes

When geocodes are wrong, they are often very wrong, and set somewhere wildly different from our target area. We do a spatial join with the DE shapefile, keep only the observations which fall outside of DE. We can check these observations and potentially geocode them manually.

We do this through the following steps:

  1. Perform a spatial join using st_join from the sf package to see which points from geocoded_de fall outside DE (excluding those we know are NA at present). To do this we need to reproject our lat/lon data as an sf vector object projected in WGS84 (web mercator, crs = 4326) and join it to de_shp.

  2. We filter those observations which come back as is.na(STUSPS) == TRUE (e.g. they don't join at all to the DE tigris shapefile and don't take on any of it's attributes like STUSPS) and know that these are the ones which fell outside our shapefile.

  3. We dplyr::select only our canonical variables from these and extract the lat/lon from their geometries using a mutate statement. We want to turn this back into a dataframe with lat/lon data and not geometries like an sf object. So we set it as.data.frame and remove the geometry column.

  4. We now rbind these data back to our geocoded_de observations which lacked lat/lon in the first place, to create a data set consisting of all our observations which we presume have bad geocodes.

errors_de <- st_join(geocoded_de %>% 
          filter(is.na(lat) == FALSE) %>% 
          st_as_sf(., coords = c("lon", "lat"), crs = 4326), 
        de_shp, 
        join = st_within, 
        left = TRUE) %>%
  filter(is.na(STUSPS) == TRUE) %>%
  mutate(lon=map_dbl(geometry, ~st_centroid(.x)[[1]]),
         lat=map_dbl(geometry, ~st_centroid(.x)[[2]]))%>%
  dplyr::select(canonical_names) %>%
  as.data.frame() %>%
  dplyr::select(-geometry) %>%
  rbind(., geocoded_de %>%
          filter(is.na(lat) == TRUE))

8.6. Manually geocode remaining observations

The observations that don't geocode will need to be dealt with manually (e.g. input lat/lon coordinates based on google map research). Similarly, observations that geocode poorly and end up on our errors_de_pre list need to be manually geocoded.

This has to be done by hand unfortuately. You can go through the observations which either have NA values for lat/lon in de_geocoded_pre or are found to be outside DE in the errors_de_pre dataframe. Here we do this with web research and mutate statements where we replace the lat/lon values for individual licenses.

In other data sets and elsewhere in this code we export the data to a csv and an RA does the coding, which we then re-import and integrate. The export approach is probably less prone to error from transcription problems.

geocoded_de_fixed <- geocoded_de %>%
  mutate(lat = ifelse(account == "2003101733", 38.816784,   lat),
         lon = ifelse(account == "2003101733", -75.299722, lon)) %>%
  mutate(lat = ifelse(account == "2020703345", 38.456186,     lat),
         lon = ifelse(account == "2020703345", -75.573836, lon)) %>%
  mutate(lat = ifelse(account == "2010104755", 38.456700,     lat),
         lon = ifelse(account == "2010104755", -75.555881, lon)) %>%
  mutate(lat = ifelse(account == "2011119693", 38.455830,    lat),
         lon = ifelse(account == "2011119693", -75.559666, lon)) %>%
  mutate(lat = ifelse(account == "2000101625", 38.458207,    lat),
         lon = ifelse(account == "2000101625", -75.559746, lon)) %>%
  mutate(lat = ifelse(account == "1989033057", 39.070951,     lat),
         lon = ifelse(account == "1989033057", -75.554093, lon))  %>%
  mutate(lat = ifelse(account == "2019101246", 39.218640,    lat),
         lon = ifelse(account == "2019101246", -75.571138, lon))%>%
  mutate(lat = ifelse(account == "2013103041", 38.509547,     lat),
         lon = ifelse(account == "2013103041", -75.237918, lon))       

8.7. Write out the dataset

This is our final DE data set - geocoded_de_fixed. It can be written out to file. We will bind it to our NJ and PA data later in the code base.

If you are running this code base to do an update, change the filepath below

write.csv(geocoded_de_fixed, "//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/dataOutputs/de_geocoded_10_13_20.csv")

9. Coalescing the master data set

If we are running a routine for all of the states at once, we can coalesce the three states and write out the data set.

allStates <- rbind(geocoded_de_fixed, geocoded_nj_fixed) %>%
  rbind(., geocoded_pa_fixed)
write.csv(allStates, "//jove.design.upenn.edu/Dept-Shares/prax/01 Project Folders/2019_Annenberg_GeoScanning/dataOutputs/all_Retailers_10_20_20.csv")

9.1 Methodology for Updating One State at a Time

It's likely that one would just run a routine for a single state and then update a portion of the data set.

The methodology for doing this would be as follows:

  1. Run the routine for the state of interest. (The below example uses NJ)

  2. Upload the allStates data set (e.g. the most current, multi-state set) and apply the cleaning convention so that the data types are correct.

  3. Add the new observations by filtering out the observations from the state of interest from allStates and using an rbind command to append the new NJ data set.

  4. Write the data to file.

allStates_updated <- read.csv("filepath goes here") %>%
  dplyr::select(canonical_names) %>%
  mutate_if(is.factor, as.character) %>%
  mutate(account = as.character(account)) %>%
  mutate(expiration_date = ymd(expiration_date),
         publish_date = ymd(publish_date)) %>%
  filter(state != "NJ") %>%
  rbind(., geocoded_nj_fixed)

write.csv(allStates_updated, "filepath goes here")

10. Make A Leaflet Map

The data can be visualized using the leaflet webmapping package for R Markdown. The following map subdivides the data into active and expired licenses (selectable using a menu at the top right) and allows the user to hover over any point and view the name and address of the license holder. The basemaps are open source and available in the leaflet.providers library.

Additional features can be added to the map, including but not limited to search functions, polygon geometries, and more.

R-Markdown documents can be knit easily into portable html files which can be sent and received easily due to their relatively small file size.

For elaborate data dashboarding, R-Shiny provides more robust joining and selecting functionality, integrated with active charts and tables.

active <- allStates %>%
  filter(expired_y_n == "ACTIVE")

expired <- allStates %>%
  filter(expired_y_n != "ACTIVE")

m4 <- leaflet() %>%
  setView(lng = -77.475127, lat = 40.726604, zoom = 07) %>%
  addProviderTiles(providers$Esri.WorldTopoMap) %>%
  addCircleMarkers(lng= as.numeric(active$lon), 
                   lat= as.numeric(active$lat), 
                   radius = 1,
                   fillOpacity = 1,
                   color= "red",
                   label = paste(active$trade_name, "|",
                           active$address_full),
                           group = "Active Retailers") %>%
  addCircleMarkers(lng= as.numeric(expired$lon), 
                   lat= as.numeric(expired$lat), 
                   radius = 0.5,
                   fillOpacity = 1,
                   color= "blue",
                   label = paste(expired$trade_name, "|",
                           expired$address_full),
                           group = "Expired Retailers") %>%
  addLayersControl(
    overlayGroups = c("Active Retailers", 
                      "Expired Retailers"),
    options = layersControlOptions(collapsed = TRUE)) %>%
  addLegend(color = "blue", 
            labels = "Expired Retailers",
            group = "Expired Retailers",
            position = "topright") %>%
  addLegend(color = "red", 
            labels = "Active Retailers",
            group = "Active Retailers",
            position = "topright")  %>% 
  hideGroup(c("Expired Retailers")) %>%
  addScaleBar(position = "topleft")

m4 # Print the map

11. Data dictionary

11.1. Data frames generated during updating routines

11.1.1 Pennsylvania

retailers_Socrata_pa - Data loaded via the SODA API for PA

stored_pa - Baseline data set

joined_pa - full join between retailers_Socrata_pa and stored_pa

to_geocode_pa - retailers in need of address geocoding

geocoded_pa - retailers which have gone through the geocoding process

errors_pa - data which might have bad geocodes and/or have resisted geocoding and need to be manually geocoded.

geocoded_pa_fixed - data for which broken geocodes have been manually fixed

11.1.2 New Jersey

retailers_new_nj - Data loaded via excel from NJ gov't

stored_nj - Baseline data set

joined_nj - full join between retailers_new_nj and stored_nj

to_geocode_nj - retailers in need of address geocoding

geocoded_nj - retailers which have gone through the geocoding process

errors_nj - data which might have bad geocodes and/or have resisted geocoding and need to be manually geocoded.

geocoded_nj_fixed - data for which broken geocodes have been manually fixed

11.1.3 Delaware

retailers_Socrata_de - Data loaded via the SODA API for DE

stored_de - Baseline data set

joined_de - full join between retailers_Socrata_de and stored_de

to_geocode_de - retailers in need of address geocoding

geocoded_de - retailers which have gone through the geocoding process

errors_de - data which might have bad geocodes and/or have resisted geocoding and need to be manually geocoded.

geocoded_de_fixed - data for which broken geocodes have been manually fixed

11.2. Canonical names for columns in our data sets

trade_name type: chr example: "1221 INC"

account type: chr example: "2005211534"

expired_y_n type: chr example: "ACTIVE"

expiration_date type: date example: 2020-12-31

lat type: chr example: "39.789164"

lon type: chr example: "-75.697388"

address_full type: chr example: "7441 LANCASTER PIKE HOCKESSIN DE 19707"

county type: chr example: "PHILADELPHIA"

publish_date type: date example: 2018-01-01

license_type type: chr example: "TOBACCO PRODUCTS RETAILER"

state type: chr example: "DE"

12. To do

Next steps

  • Update data sets with new NJ data
  • Run PA and DE updates
  • Incorporate these new data into geoscan routines
  • Add NYU routines into our exposure routines - stay events etc
  • Begin analyzing existing collections of data
  • Add more built environment and risk-terrain functionality
LS0tDQp0aXRsZTogIkdlb3NjYW5uaW5nIFJldGFpbGVyIERhdGFiYXNlIFdvcmtmbG93IC0gVkVSU0lPTiAzIg0KYXV0aG9yOiAiTWljaGFlbCBGaWNobWFuIg0KZGF0ZTogIk9jdG9iZXIgMTR0aCwgMjAyMCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0UsbWVzc2FnZSA9IEZBTFNFLGNhY2hlPVRSVUV9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpvcHRpb25zKHNjaXBlbj05OTkpDQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KbGlicmFyeShybWFya2Rvd24pDQoNCmxvYWQoIi8vam92ZS5kZXNpZ24udXBlbm4uZWR1L0RlcHQtU2hhcmVzL3ByYXgvMDEgUHJvamVjdCBGb2xkZXJzLzIwMTlfQW5uZW5iZXJnX0dlb1NjYW5uaW5nL3dvcmtzcGFjZXMvcmV0YWlsZXJfdXBkYXRlc18xMF8xM18yMC5SRGF0YSIpDQpgYGANCg0KIzEuIEludHJvZHVjdGlvbg0KDQpUaGlzIGRvY3VtZW50IGRldGFpbHMgdGhlIGNvZGUgdXNlZCB0byBpbnRha2UsIGNsZWFuLCBnZW9sb2NhdGUgYW5kIGNvYWxlc2NlIGFkbWluaXN0cmF0aXZlIGRhdGEgb24gdG9iYWNjbyByZXRhaWwgbGljZW5zZXMgZnJvbSB0aHJlZSBzdGF0ZXMgLSBQZW5uc3lsdmFuaWEsIE5ldyBKZXJzZXkgYW5kIERlbGF3YXJlLiBUaGVzZSBkYXRhIHdpbGwgYmUgdXNlZCB0byByZWxhdGUgdGhlIGdlby1sb2NhdGlvbiBpbmZvcm1hdGlvbiBwcm92aWRlZCBieSBHZW9TY2FubmluZyBwcm9qZWN0IHN1YmplY3RzIHRvIHJldGFpbGVyIGxvY2F0aW9ucyBhY2NvcmRpbmcgdG8gZXhwZXJpbWVudGFsIHBhcmFtZXRlcnMgdG8gYmUgc3BlY2lmaWVkLg0KDQpUaGlzIGNvZGViYXNlIGRvZXMgdHdvIHRoaW5nczoNCg0KMS4gQ3JlYXRlcyBhbiBpbml0aWFsIGRhdGFiYXNlIG9mIHJldGFpbGVycyB3aGljaCBjYW4gYmUgdXBkYXRlZCBhcyBuZXcgZGF0YSBhcmUgcHVibGlzaGVkLiAoVGhpcyAiYmFzZWxpbmUiIGRhdGEgc2V0IGNvbnNpc3RzIG9mIDMxLDAzNCByZXRhaWxlcnMsIG9mIHdoaWNoIG9ubHkgMTAgY291bGQgbm90IGJlIGdlb2xvY2F0ZWQgYWZ0ZXIgcXVhbGl0YXRpdmUgcmVzZWFyY2guKQ0KDQoyLiBDcmVhdGVzIGEgcHJvY2VzcyBieSB3aGljaCBuZXcgZGF0YSBzZXRzIC0gcHVibGlzaGVkIGJ5IFBBIGFuZCBERSB1c2luZyBvcGVuIHNvdXJjZSBBcHBsaWNhdGlvbiBQcm9ncmFtbWluZyBJbnRlcmZhY2VzIChBUElzKSwgYW5kIHByb3ZpZGVkIGJ5IGRpcmVjdCBjb21tdW5pY2F0aW9uIGZyb20gTmV3IEplcnNleSAtIGFyZSBjcm9zcy1jaGVja2VkIGFnYWluc3QgZXhpc3RpbmcgZGF0YS4gDQoNCk5ldyByZXRhaWxlcnMgYXJlIGFkZGVkIHRvIHRoZSBkYXRhYmFzZSwgb2xkIG9uZXMgYXJlIG1hcmtlZCBhcyBjbG9zZWQsIHJlcGVhdCBlbnRyaWVzIGFyZSBsb2dnZWQgYXMgY29udGludWluZyB0aGVpciByZXRhaWwgYWN0aXZpdHkuIEFsbCBvYnNlcnZhdGlvbnMgYXJlIHRoZW4gZ2VvLWxvY2F0ZWQgYW5kIGVycm9ycyBjaGVja2VkIGFuZCBjb3JyZWN0ZWQgLSBmaXJzdCBhdXRvbWF0aWNhbGx5IGFuZCB0aGVuIG1hbnVhbGx5LiBUaGlzIHVwZGF0aW5nIHByb3RvY29sIHNob3VsZCAqaHlwb3RoZXRpY2FsbHkqIGJlIHJlcHJvZHVjaWJsZS4NCg0KVGhpcyBkb2N1bWVudCBpcyBnZW5lcmFsbHkgb3JnYW5pemVkIGJ5IHN0YXRlIC0gd2l0aCBlYWNoIHNlY3Rpb24gZG9jdW1lbnRpbmcgYSkgdGhlIGNyZWF0aW9uIG9mIGEgYmFzZWxpbmUgZGF0YSBzZXQgYW5kIGIpIHRoZSB1cGRhdGUgcm91dGluZSBmb3IgdGhhdCBzdGF0ZS4NCg0KVGhlIGVuZCBvZiB0aGUgZG9jdW1lbnQgY29udGFpbnMgY29kZSBmb3IgbWFraW5nIGludGVyYWN0aXZlIG1hcHMgYW5kIGFsc28gaGFzIGEgc2V0IG9mIGRhdGEgZGljdGlvbmFyaWVzLg0KDQpTdGF0ZXMgY2FuIGFsbCBiZSB1cGRhdGVkIGF0IG9uY2UsIGJ1dCBtb3JlIGxpa2VseSB0aGV5IGNhbiBiZSBkb25lIG9uZSBhdCBhIHRpbWUgLSBjb2RlIGZvciBkb2luZyBvbmUgc3RhdGUgaW4gYSBzaXR0aW5nICh1cGxvYWRpbmcgdGhlIHdob2xlIGJhc2VsaW5lIHNldCBhbmQganVzdCByZXBsYWNpbmcgb25lIHN0YXRlKSBpcyBpbmNsdWRlZCBuZWFyIHRoZSBlbmQgb2YgdGhpcyBkb2N1bWVudC4NCg0KVGhlc2UgcmV0YWlsZXIgZGF0YWJhc2VzIGFyZSBkZXNpZ25lZCB0byBiZSBpbnRlZ3JhdGVkIGludG8gYSBicm9hZGVyIGNvZGUgYmFzZSB3aGljaCBpbnRha2VzIGFuZCBhbmFseXplcyBnZW8tbG9jYXRpb24gZGF0YSBhbmQgcmVsYXRlcyB0aGVtIHRvIHRoZSByZXRhaWxlciBpbmZvcm1hdGlvbi4gTm90IG9ubHkgZG8gdGhlc2UgZGF0YSBoYXZlIGxhdC9sb24gd2hpY2ggZW5hYmxlIGdlb2dyYXBoaWMgcmVsYXRpb25zaGlwcywgYnV0IHRoZXkgbm93IGhhdmUgYSB0aW1lLXdpbmRvdyBhc3NvY2lhdGVkIHdpdGggbGljbmVzdXJlIGF0IHRoYXQgbG9jYXRpb24sIGFsbG93aW5nIGZvciBhIHNlY29uZCBjaGVjayBhZ2FpbnN0IGdlby1sb2NhdGlvbiBpbmZvIHRvIGNvbmZpcm0gImV4cG9zdXJlIiB0byBwb2ludC1vZi1zYWxlIHRvYmFjY28gbG9jYXRpb25zLg0KDQpEdXJpbmcgdGhlIHN1bW1lciBhbmQgZmFsbCBvZiAyMDE5LCBNaWNoYWVsIEZpY2htYW4sIGNpdHkgcGxhbm5pbmcgcmVzZWFyY2hlciBhbmQgYW5hbHlzdCBhdCBQZW5uUHJheGlzIChvZiB0aGUgV2VpdHptYW4gU2Nob29sIG9mIERlc2lnbiksIHRvb2sgcHJlLWV4aXN0aW5nIGNvZGViYXNlIHJlbHRhZWQgdG8gZ2VvLWxvY2F0aW9uIGRhdGEgZGV2ZWxvcGVkIGZvciB0aGUgcHJvamVjdCBhbmQgdXBkYXRlZCBpdCB0byBjb25mb3JtICh3aGVyZSBwb3NzaWJsZSkgdG8gbW9kZXJuIGB0aWR5dmVyc2VgIGRhdGEgd3JhbmdsaW5nIHN0YW5kYXJkcyBhbmQgdGhlIHBvd2VyZnVsIGBzZmAgc3BhdGlhbCBwYWNrYWdlIHdoaWNoIGlzIGludGVncmF0ZWQgaW4gdG8gdGhlIGB0aWR5dmVyc2VgIGFuZCBSJ3MgcG93ZXJmdWwgdmlzdWFsaXphdGlvbiBwYWNrYWdlIGBnZ3Bsb3QyYC4NCg0KVGhlIG5leHQgc3RlcCBpcyB0byBpbnRlZ3JhdGUgdGhlc2UgcmV0YWlsZXIgZGF0YSwgaW4gdGhlaXIgbm93LWNvbXBsZXRlIGZvcm0sIHRvIGdlby1sb2NhdGlvbiBkYXRhLCBhbmQgaW50ZWdyYXRlIG1lYXN1cmVzIGRldmVsb3BlZCBieSBOWVUgaW5jbHVkaW5nICJzdGF5IGV2ZW50IiBkZXRlY3Rpb25zIG9mIHNlZGVudGFyeSBiZWhhdmlvciBpbiBhZGRpdGlvbiB0byBwcmV2aW91c2x5IGNyZWF0ZWQgbWVjaGFuaXNtcyBmb3Igc3BhdGlhbCBqb2lucyBiZXR3ZWVuIHJldGFpbGVyIGJ1ZmZlcnMgYW5kIGdlby1sb2NhdGlvbiBkYXRhLg0KDQpBdCB0aGUgcHJlc2VudCBkYXRlLCB0aGlzIGlzIGEgZG9jdW1lbnQtaW4tcHJvZ3Jlc3MgYW5kIHRoZSBjb2RlIGNvbnRhaW5lZCBoZWlyZWluIGlzIGRlc2lnbmVkIHRvIG9wZXJhdGUgb24gdGVzdCBnZW8gZGF0YSBhbmQgYSB0ZW1wb3JhcnkgZGF0YSBzZXQgb2YgdG9iYWNjbyByZXRhaWxlciBsb2NhdGlvbnMuIFN1YnNlcXVlbnQgc3RlcHMgdG8gY29kZSBiYXNlIGRldmVsb3BtZW50IHdpbGwgaW5jbHVkZSB0aGUgY3JlYXRpb24gb2YgYSBzdWl0ZSBvZiBkZXNjcmlwdGl2ZSBzdGF0aXN0aWNzIGZvciBzdGFuZGFyZCBkYXRhIGludGFrZSBhbmQgYW5hbHlzaXMgcHJvY2VkdXJlcywgdGhlIGNyZWF0aW9uIG9mIGFuIGludGVyYWN0aXZlIG1hcCBhcyBwYXJ0IG9mIGEgc3ViamVjdCBpbnRha2UgcHJvdG9jb2wgYW5kIHRoZSBkZXZlbG9wbWVudCBvZiBhICJzcGF0aWFsIHJpc2sgc3VyZmFjZSIgYW5hbHl0aWMgYXBwcm9hY2guIEZ1cnRoZXJtb3JlLCByZXRhaWxlciBpbmZvcm1hdGlvbiBmb3IgYSBtdWx0aS1zdGF0ZSBzdHVkeSBhcmVhIHdpbGwgbmVlZCB0byBiZSBydW4gdGhyb3VnaCB0aGUgcHJvdG9jb2wuDQoNCiMjIDEuMS4gQWJvdXQgdGhpcyBjb2RlIGJhc2UNCg0KVGhpcyBpcyBhICJmbGV4aWJsZSIgY29kZSBiYXNlIHJhdGhlciB0aGFuIGEgc2VyaWVzIG9mIHRpZ2h0bHktd3JhcHBlZCBmdW5jdGlvbnMgdGhhdCB0YWtlIGEgc2VyaWVzIG9mIGlucHV0cyBhbmQgcGFyYW1ldGVycyBhbmQgb3V0cHV0IGFuIG9iamVjdC4gVGhpcyB0eXBlIG9mIGNvZGUgYmFzZSBpcyBhIGJpdCBoYXJkZXIgdG8gdXNlIHdpdGhvdXQgYSBzdXJmYWNlIHVuZGVyc3RhbmRpbmcgb2YgUiwgYnV0IG11Y2ggbW9yZSBhZGFwdGFibGUgdG8gbmV3IGludGFrZSBkYXRhIHR5cGVzIGFuZCBzdHJ1Y3R1cmVzLCB1cGRhdGVzIGFuZCBuZXcgYW5hbHlzZXMuIEZvciBleGFtcGxlLCB0aGlzIGNvZGUgd2lsbCBoYXZlIGZld2VyIHRpZ2h0bHkgd3JpdHRlbiBmdW5jdGlvbnMgdGhhdCBjYW4gYmUgc2ltcGx5IGV4ZWN1dGVkLCBidXQgYXJlIHZlcnkgZGlmZmljdWx0IHRvIHRyb3VibGVzaG9vdCBpbiB0aGUgZXZlbnQgb2YgYmFkIGRhdGEgb3IgY2hhbmdlcyB0byB0aGUgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UuIA0KDQpUaGUgdXNlciBuZWVkcyB0byBiZSBhYmxlIHRvIG9wZXJhdGUgdGhlIFIgc3R1ZGlvIGVudmlyb25tZW50LCBleGVjdXRlIGNvZGUgaW4gdGhlIFIgc3R1ZGlvIGNvbnNvbGUsIGluc3RhbGwgYW5kIGxvYWQgcGFja2FnZXMsIHNldCBmaWxlcGF0aHMgZm9yIHVwbG9hZGluZyBhbmQgZXhwb3J0aW5nIGRhdGEsIGNoYW5nZSB2YXJpYWJsZSBhbmQgb2JqZWN0IG5hbWVzIGFuZCBwZXJmb3JtIGEgYmFzaWMgbGV2ZWwgb2YgdHJvdWJsZXNob290aW5nIHVzaW5nIGludGVybmV0IGZvcnVtcyAobGlrZSBTdGFjayBPdmVyZmxvdywgZ2l0aHViIGV0YykgYW5kIFIgZG9jdW1lbnRhdGlvbiBpZiBuZWNlc3NhcnkuDQoNClRoYXQgc2FpZCwgdGhpcyBjb2RlIGlzIG9wZW4gc291cmNlIGFuZCBzdWJqZWN0IHRvIGRlcHJlY2F0aW9uLCBwYWNrYWdlIHVwZGF0ZXMgZXRjLiwgYW5kIGlzIHRoZXJlZm9yZSBwcm92aWRlZCB3aXRoIG5vIGRlZmluaXRpdmUgd2FycmFudHkuIFRoaXMgZG9jdW1lbnQgaXMgZGVzaWduZWQgdG8gYW5ub3RhdGUgdGhlIGNvZGUgYmFzZSB0byBiZSB1bmRlcnN0YW5kYWJsZSwgcmVwcm9kdWNpYmxlIGFuZCByZWZsZWN0aXZlIG9mIHJlc2VhcmNoIG5lZWRzLg0KDQpUaGUgZWxlbWVudHMgb2YgdGhpcyBjb2RlYmFzZSBjYW4gYmUgdXNlZCBvdXQgb2Ygb3JkZXIgLSB3aXRoIHN0YXRlcyB1cGRhdGVkIG9uZSBzdGF0ZSBhdCBhIHRpbWUuIEZ1cnRoZXJtb3JlLCB0aGUgZ2VuZXJhbCBvdXRsaW5lIG9mIHRoZSBsZWFmbGV0IGNvZGUgaW4gY2FuIGJlIHVzZWQgYXQgYW55IHN0YWdlIG9mIHRoZSBwcm9jZXNzIHRvIGNoZWNrIHRoZSByZXRhaWxlciBsb2NhdGlvbnMgZm9yIGdyb3NzIGVycm9ycyBhbmQgZXhwbG9yZSB0aGVtIG1vcmUgZGVlcGx5Lg0KDQojIyAxLjIuIFVzaW5nIFRoaXMgRG9jdW1lbnQNCg0KVGhpcyBkb2N1bWVudCBpcyBbYW4gUiBNYXJrZG93biBkb2N1bWVudF0oaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvcm1hcmtkb3duLyksIHdoaWNoIGlzIGEgcHVibGlzaGluZyBsYW5ndWFnZSBkZXNpZ25lZCBlbWJlZCBjaHVua3Mgb2YgUiBjb2RlLiBJdCBjYW4gYWxzbyBiZSB1c2VkIHRvIGNyZWF0ZSByZXByb2R1Y2libGUgcmVwb3J0cyB1bmRlcmdpcmRlZCBieSBSIGNvZGUuDQoNCkNvZGUgY2FuIGJlIGN1dCBhbmQgcGFzdGVkIGZyb20gdGhpcyBkb2N1bWVudCBpbnRvIGEgcmVndWxhciBSIGNvZGUgZmlsZSBvciB0aGUgUiBjb25zb2xlLg0KDQpVc2UgdGhlIHRhYmxlIG9mIGNvbnRlbnRzIGF0IHRoZSBsZWZ0IGhhbmQgc2lkZSB0byBuYXZpZ2F0ZSB0aHJvdWdoIHRoaXMgZG9jdW1lbnQuDQoNCiMjIDEuMy4gV29ya2Zsb3cNCg0KMS4gQ3JlYXRlIGEgYmFzZWxpbmUgZGF0YSBzZXQgZnJvbSBwcmV2aW91c2x5IHB1Ymxpc2hlZCBsaWNlbnNlIGRhdGEsIHN0YW5kYXJkaXplIHRoZSBjb2x1bW4gbmFtZXMgYW5kIGdlb2xvY2F0ZSBhbGwgb2YgdGhlIG9ic2VydmF0aW9ucyB3aXRoIGxhdC9sb24gdXNpbmcgdGhlIEdvb2dsZSBnZW9jb2RpbmcgQVBJLiBXZSByZWZlciB0byB0aGlzIGFzIGEgInByZWN1cnNvciByb3V0aW5lIiBhbmQgaXQgaXMgZG9uZSBmb3IgZWFjaCBvZiBvdXIgdGhyZWUgc3RhdGVzLiAqKlRoaXMgY29kZSBpcyBzaG93biBoZXJlIGZvciBkb2N1bWVudGF0aW9uIHB1cnBvc2VzLCBidXQgc2hvdWxkbid0IG5lZWQgdG8gYmUgcmVwbGljYXRlZCBpbiB0aGUgZnV0dXJlLioqDQoNCjIuIEludGFrZSBuZXcgbGljZW5zaW5nIGluZm9ybWF0aW9uIGRpcmVjdGx5IGZyb20gUEEgYW5kIERFIHVzaW5nIHRoZSBTb2NyYXRhIE9wZW4gRGF0YSBBUEkgKFNPREEpIGFuZCBmcm9tIE5ldyBKZXJzZXkgdGhyb3VnaCBkaXJlY3QgY29tbXVuaWNhdGlvbi4gVGhlIGJhc2ljIHdvcmtmbG93IGhlcmUgaXMgdG8gcmVhZCBpbiBganNvbmAgZGF0YSBmcm9tIHRoZSBBUEksIHN0YW5kYXJkaXplIGNvbHVtbiBuYW1lcyB0byBjb25mb3JtIHRvIG91ciBzdGFuZGFyZHMsIGNvbnZlcnQgaXQgdG8gYHNmYCBkYXRhIHR5cGUsIHRpbWVzdGFtcCBpdCwgd3JpdGUgaXQgb3V0IHdpdGggYSBkYXRlIHN0YW1wIGZvciB0b2RheSdzIGRhdGUgYWxvbmcgd2l0aCB0aGUgZXhwaXJhdGlvbiBkYXRlLg0KDQozLiBDb21wYXJlIHRoZSBsaWNlbnNlcyBpbiB0aGUgbmV3IGRhdGEgdG8gdGhvc2UgaW4gdGhlIGJhc2VsaW5lIGRhdGEgYW5kIGxvZyB0aGVpciBzdGF5IGluIHRoZSBkYXRhYmFzZS4gSWYgYSBsaWNlbnNlZSBpcyBuZXcsIGNyZWF0ZSBhbiBlbnRyeSBmb3IgaXQuIElmIGEgbGljZW5zZWUgbm8gbG9uZ2VyIGhhcyBhIHJlZ2lzdHJhdGlvbiBpbiB0aGUgbmV3IGRhdGEsIG1hcmsgdGhlaXIgbGljZW5zZSBhcyAiRVhQSVJFRCIuIElmIGEgbGljZW5zZWUgaXMgc3RpbGwgZm91bmQgYW5kIHVuY2hhbmdlZCwgZXh0ZW5kIHRoZSB0aW1lIG9mIGl0cyBzdGF5IGluIHRoZSBkYXRhYmFzZQ0KDQo0LiBHZW9sb2NhdGUgdGhlIHJldGFpbGVyIGRhdGEgYnkgc2VuZGluZyBhbnkgYWRkcmVzcyB3aGljaCBsYWNrIGxhdC9sb24gaW5mb3JtYXRpb24gdG8gdGhlIEdvb2dsZSBnZW9jb2RpbmcgQVBJLCB3aGljaCBpcyB0aGUgaW5kdXN0cnkgc3RhbmRhcmQgc2VydmljZS4NCg0KNS4gQ2hlY2sgZXJyb3JzIHVzaW5nIGFuIGF1dG9tYXRlZCBwcm9jZXNzIC0gZmlndXJpbmcgb3V0IGlmIHRoZXJlIGFyZSBwb2ludHMgd2hpY2ggYXJlIG91dHNpZGUgb2YgdGhlIHN0YXRlIGluIHdoaWNoIHRoZXkgYXJlIHN1cHBvc2VkIHRvIGJlIGxvY2F0ZWQuIE9mdGVuIHdoZW4gdGhlcmUgYXJlIGVycm9ycyB3aXRoIGdlb2xvY2F0ZWQgZGF0YSAtIHRoZSBlcnJvcnMgYXJlIHF1aXRlIGJpZyAtIGFjcm9zcyB0aGUgY291bnRyeSwgbm90IGp1c3QgYWNjcm9zcyB0aGUgc3RyZWV0Lg0KDQo2LiBGaXggZXJyb3JzIChiYWQgZ2VvY29kZXMgYW5kIG1pc3NpbmcgYWRkcmVzcy9nZW8tZGF0YSkgbWFudWFsbHkgdXNpbmcgZ29vZ2xlIG1hcHMgYW5kIHJlc2VhcmNoIHVzaW5nIHN0YXRlIGJ1c2luZXNzIGRhdGFiYXNlcywgYnVzaW5lc3MgbGlzdGluZ3MgYW5kIG90aGVyIG1ldGhvZHMuIFBlcmlvZGljYWxseSBjcmVhdGUgbGVhZmxldCBtYXBzIHRvIGV5ZS10ZXN0IHRoZSBjaGVja2luZy4NCg0KNy4gV3JpdGUgb3V0IHRoZSBkYXRhIGZvciBlYWNoIHN0YXRlIHRvIGZpbGUuDQoNCjguIENvYWxlc2NlIHRoZSBkYXRhIGludG8gYSBtdWx0aS1zdGF0ZSBkYXRhIHNldCBhbmQgd3JpdGUgaXQgdG8gZmlsZS4NCg0KOS4gTWFwIHRoZSBkYXRhIHVzaW5nIHRoZSBgbGVhZmxldGAgd2ViIG1hcHBpbmcgcGFja2FnZSBmb3IgZGlzcGxheSBhbmQgdXNlIGR1cmluZyBzdHVkeSBpbnRha2UgT1IgZHVyaW5nIHRoZSBlcnJvciBjaGVja2luZyBwcm9jZXNzICh0byBleGFtaW5lIGluZGl2aWR1YWwgb2JzZXJ2YXRpb25zLikuIFRoaXMgbGVhZmxldCBtYXAgY2FuIGJlIHJ1biBhdCBhbnkgdGltZSBpbiBvcmRlciB0byBjaGVjayBlcnJvcnMgKHNlZSBzdGVwIDYpDQoNCiMgMi4gU2V0dXANCg0KIyMgMi4xLiBMb2FkIExpYnJhcmllcw0KDQpgYGB7ciBsaWJyYXJpZXNfYW5kX2Vudmlyb25tZW50LCByZXN1bHRzID0gJ2hpZGUnLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9IEZBTFNFLGNhY2hlPVRSVUV9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KHRpZ3JpcykNCmxpYnJhcnkodGlkeWNlbnN1cykNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShnZ21hcCkNCmxpYnJhcnkoanNvbmxpdGUpDQpsaWJyYXJ5KFJTb2NyYXRhKQ0KbGlicmFyeShyZWFkeGwpDQpsaWJyYXJ5KGxlYWZsZXQpDQpsaWJyYXJ5KGxlYWZsZXQucHJvdmlkZXJzKQ0KbGlicmFyeShyZWFkeGwpDQpgYGANCg0KYHRpZHl2ZXJzZWAgLSBSJ3MgbXV0dWFsbHkgaW50ZWxsaWdpYmxlIHdvcmxkIG9mIGRhdGEtd3JhbmdsaW5nIGFuZCBncmFwaGljcyBwYWNrYWdlcyBpbmNsdWRpbmcgYGRwbHlyYA0KDQpgc2ZgIC0gVGhlIHByZW1pZXIgc3BhdGlhbCBwYWNrYWdlIGZvciBSLCB3aGljaCBwbGF5cyB3ZWxsIHdpdGggYGdncGxvdDJgLCBgZHBseXJgIGFuZCB0aGUgb3RoZXIgdGlkeXZlcnNlIHBhY2thZ2VzLiBJbmNsdWRlcyBnZW9wcm9jZXNzaW5nIGNhcGFiaWxpdGllcyBmb3IgcG9pbnQgYW5kIHBvbHlnb24gZ2VvbWV0cmllcy4NCg0KYHRpZ3Jpc2AgLSBBIHBhY2thZ2UgZm9yIGNhbGxpbmcgc2hhcGVmaWxlcyBmcm9tIHRoZSBVUyBEZXB0LiBvZiBDb21tZXJjZSwgaW5jbHVkaW5nIHBvbGl0aWNhbCBib3VuZGFyaWVzLCBzY2hvb2wgZGlzdHJpY3RzIGFuZCBoeWRyb2xvZ3kNCg0KYHRpZHljZW5zdXNgIC0gQSBgdGlkeWAgYW5kIGBzZmAgY29tcGF0aWJsZSBwYWNrYWdlIGZvciBjYWxsaW5nIHRoZSBVUyBDZW5zdXMgQVBJIGZvciBhbnkgYXZhaWxhYmxlIGRhdGEgZnJvbSB0aGUgQUNTIGFuZCBkZWNlbm5pYWwgY2Vuc3VzLCAyMDAwIHRvIHByZXNlbnQuIEluY2x1ZGVzIGdlb21ldHJpZXMgZnJvbSB0cmFjdHMgdG8gY291bnRpZXMgdG8gc3RhdGVzIGlmIGRlc2lyZWQuDQoNCmBsdWJyaWRhdGVgIC0gQSBzb3BoaXN0aWNhdGVkIHlldCBzaW1wbGUgZGF0ZS1oYW5kbGluZyBwYWNrYWdlDQoNCmBnZ21hcGAgLSBBbiBvbGRlciBtYXBwaW5nIGdyYXBoaWNzIHBhY2thZ2UgZGVzaWduZWQgZm9yIHVzZSB3aXRoIGdncGxvdCwgd2hpY2ggaGFzIGJlZW4gc3VycGFzc2VkIGJ5IGBzZmAgYnV0IGFsbG93cyBmb3IgZ2VvY29kaW5nIHRvIEFQSXMgc3VjaCBhcyBHb29nbGUgTWFwcyBhbmQgdGhlIERhdGEgU2NpZW5jZSBUb29sa2l0Lg0KDQpganNvbmxpdGVgIC0gQSBwYWNrYWdlIGRlc2lnbmVkIHRvIHVwbG9hZCwgY29udmVydCBhbmQgaGFuZGxlIEpTT04gZmlsZXMgaW4gUg0KDQpgUlNvY3JhdGFgIC0gQSBwYWNrYWdlIHVzZWQgdG8gaW50ZXJhY3Qgd2l0aCBBUElzIGNvbW1vbmx5IHVzZWQgdG8gaG9zdCBvcGVuIGRhdGEgc2V0cy4gUGVubnN5bHZhbmlhIGFuZCBEZWxhd2FyZSB1c2UgIlNPREEiIC0gdGhlIFNvY3JhdGEgT3BlbiBEYXRhIEFQSSBhcyBhbiBlbmRwb2ludCBmb3IgaXRzIGRhdGEuDQoNCmByZWFkeGxgIC0gQSBwYWNrYWdlIGZvciByZWFkaW5nIEV4Y2VsIGZpbGVzIHRvIHRpYmJsZS9kYXRhZnJhbWUNCg0KYGxlYWZsZXRgIC0gT25lIG9mIHRoZSBtb3N0IGNvbW1vbiBqYXZhc2Nyb3B0L2h0bWwvY3NzIG1hcHBpbmcgcGFja2FnZXMgZm9yIHdlYiB1c2UuIFIgTWFya2Rvd24gc3VwcG9ydHMgbGVhZmxldCBmdW5jdGlvbmFsaXR5IGFuZCBkb2VzIG5vdCByZXF1aXJlIHRoZSB1c2Ugb2YgamF2YXNjcmlwdC4gTGVhZmxldCBkb2VzIG5vdCBzdXBvcnQgYHNmYCBhbmQgYW55IHZlY3RvciBkYXRhIGJleW9uZCBwb2ludHMgbmVlZHMgdG8gdXNlIGByZ2RhbGAsIGFuIG9sZGVyIHNwYXRpYWwgcGFja2FnZS4gDQoNCmBsZWFmbGV0LnByb3ZpZGVyc2AgLSBhZGRpdGlvbmFsIGJhc2VtYXBzIGFuZCBzdHlsaW5nIG9wdGlvbnMgZm9yIGxlYWZsZXQsIGluY2x1ZGluZyBFU1JJIGRhdGENCg0KIyMgMi4yLiBSZWdpc3RlciBHb29nbGUgQVBJIEtleQ0KDQpVbnRpbCByZWNlbnRseSB0aGVyZSB3ZXJlIHNldmVyYWwgZ2VvY29kZXJzIG9mIHJlYXNvbmFibGUgcXVhbGl0eSB0aGF0IHdlcmUgYXZhaWxhYmxlIGZvciBmcmVlLCBlc3BlY2lhbGx5IGZvciBzbWFsbCBydW5zLiBUbyBkZXRlciBkZXZlbG9wZXJzIGZyb20gbWFraW5nIGVuZGxlc3MgQVBJIGNhbGxzLCBoaWdoIHF1YWxpdHkgc2VydmljZXMgc3VjaCBhcyB0aGUgR29vZ2xlIGdlb2RvY2lkbmcgQVBJIGFyZSBhdmFpbGFibGUgb25seSBmb3IgYSBmZWUuDQoNClRoaXMgaXMgYSBnZW9jb2RpbmctaW50ZW5zaXZlIHByb2plY3QsIHdpdGggdGhvdXNhbmRzIG9mIG9ic2VydmF0aW9ucyByZXF1aXJpbmcgZ2VvY29kaW5nLiBUaGUgYGdnbWFwYCBwYWNrYWdlIGFsbG93cyBmb3Igc2ltcGxlIGdlb2NvZGluZyBieSBhZGRyZXNzIGluIFIsIHdpdGggdGhlIGdvb2dsZSBBUEkgYXMgb25lIG9mIHRoZSBhdmFpbGFibGUgZXh0ZW5zaW9ucy4NCg0KWW91IGNhbiBnZXQgeW91ciBvd24ga2V5IGJ5IHNpZ25pbmcgdXAgZm9yIGEgZ29vZ2xlIGRldmVsb3BlciBhY2NvdW50LCB3aGljaCBnZXRzIHlvdSBhIDMwMCBkb2xsYXIgc3RhcnRpbmcgY3JlZGl0LiBBIHRob3VzYW5kIHJlcXVlc3RzIGNvc3QgNSBkb2xsYXJzLiBUaGlzIHNob3VsZCBiZSBzdWZmaWNpZW50Lg0KDQpJZiB5b3UgYXJlIHJ1bm5pbmcgdGhpcyBjb2RlYmFzZSwgY29uc3VsdCB3aXRoIG1lIGZvciBteSBBUEkga2V5LCBvciBnZXQgeW91ciBvd24uDQoNCkluZm9ybWF0aW9uIGFib3V0IHVzaW5nIHRoZSBnZ21hcCBwYWNrYWdlIGFuZCB0aGUgZ29vZ2xlIEFQSSBjYW4gYmUgZm91bmQgYXQgdGhlIFsnci1ibG9nZ2VycyddKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tL2dlb2NvZGluZy13aXRoLWdnbWFwLWFuZC10aGUtZ29vZ2xlLWFwaS8pIHNpdGUuIFlvdSBjYW4gZm9sbG93IHRoYXQgbGluayB0byBnZXQgeW91ciBvd24gYWNjb3VudCBzdGFydGVkLg0KDQpCZWdpbiBieSByZWdpc3RlcmluZyB5b3VyIGtleSB3aXRoIHRoZSBgZ2dtYXBgIGZ1bmN0aW9uIGByZWdpc3Rlcl9nb29nbGVgDQoNCmBgYHtyIHJlZ2lzdGVyX2tleSwgZXZhbD1GQUxTRSwgY2FjaGU9VFJVRSwgaW5jbHVkZT1UUlVFfQ0KcmVnaXN0ZXJfZ29vZ2xlKGtleSA9ICJZT1VSIEFQSSBLRVkgR09FUyBIRVJFIikNCmBgYA0KDQpgYGB7ciByZWdpc3Rlcl9rZXkyLCBldmFsPVRSVUUsIGNhY2hlPVRSVUUsIGluY2x1ZGU9RkFMU0V9DQpyZWdpc3Rlcl9nb29nbGUoa2V5ID0gIkFJemFTeURHaHliMTFWRHI2bnVha21pbEVGNEJtbU5ZX0FtaEtydyIpDQpgYGANCg0KIyMgMi4zLiBDcmVhdGUgQ2Fub25pY2FsIE5hbWVzDQoNCldlIGNyZWF0ZSBhIGxpc3Qgb2YgdmFyaWFibGUgbmFtZXMgd2hpY2ggb3VyIGZpbmFsIGRhdGEgc2V0cyB3aWxsIHVzZS4gRWFjaCBzdGF0ZSB1c2VzIGRpZmZlcmVudCBuYW1lcyB0byBkZXNjcmliZSBzaW1pbGFyIGZpZWxkcyAoZS5nLiBgbGF0aXR1ZGVgIHZzIGBsYXRgKSBhbmQgdGhlc2Ugc3RhbmRhcmRzIGNhbiBjaGFuZ2UgZnJvbSB5ZWFyIHRvIHllYXIuIFdlIHdpbGwgY3JlYXRlIG91ciBvd24gZGF0YSBzdGFuZGFyZCB0byB3aGljaCB3ZSB3aWxsIGZvcmNlIGVhY2ggZGF0YSBzZXQgdG8gY29uZm9ybS4NCg0KVGhlIGZpZWxkIG5hbWVzIGFyZSBzb21ld2hhdCBzZWxmLWV4cGxhbmF0b3J5Lg0KDQpgYGB7ciBjYW5vbmljYWxfbmFtZXMsIGNhY2hlID0gVFJVRX0NCmNhbm9uaWNhbF9uYW1lcyA8LSBjKCJjb3VudHkiLCAidHJhZGVfbmFtZSIsICJhY2NvdW50IiwgImxpY2Vuc2VfdHlwZSIsDQogICAgICAgICAgICAgICAgICAgICAiZXhwaXJhdGlvbl9kYXRlIiwgImxhdCIsICJsb24iLCAiYWRkcmVzc19mdWxsIiwNCiAgICAgICAgICAgICAgICAgICAgICJwdWJsaXNoX2RhdGUiLCAic3RhdGUiLCAiZXhwaXJlZF95X24iKQ0KYGBgDQoNCiMjIDIuNC4gTG9hZCBTdGF0ZSBTaGFwZWZpbGVzDQoNCkluIG9yZGVyIHRvIGRvdWJsZSBjaGVjayBvdXIgZ2VvY29kZWQgcmV0YWlsZXIgbG9jYXRpb25zIHRvIGRldGVjdCBzZXJpb3VzIGVycm9ycywgd2UgbG9hZCBzaGFwZWZpbGVzIGZvciBVUyBzdGF0ZSBib3VuZGFyaWVzIGZyb20gdGhlIFVTIENlbnN1cyBCdXJlYXUgdXNpbmcgdGhlIGB0aWdyaXNgIHBhY2thZ2UgdG8gaW50ZXJhY3Qgd2l0aCB0aGVpciBBUEkgKHRoaXMgZG9lcyBub3QgcmVxdWlyZSBhIENlbnN1cyBBUEkgS2V5KS4gV2UgZG93bmxvYWQgdGhlIGNvbXBsZXRlIHNoYXBlZmlsZSwgdHJhbnNmb3JtIGl0IHRvIGFuIHNmIG9iamVjdCwgYW5kIHRoZW4gc3Vic2V0IGVhY2ggc3RhdGUgYW5kIHJlcHJvamVjdCB0aGUgc3BhdGlhbCBkYXRhIHRvIFdHUzg0IChha2EgV2ViIE1lcmNhdG9yKSB1c2luZyB0aGUgYHN0X3RyYW5zZm9ybWAgZnVuY3Rpb24gZnJvbSB0aGUgYHNmYCBwYWNrYWdlLg0KDQpgYGB7ciBkb3dubG9hZF9zdGF0ZV9zaHAsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBjYWNoZSA9IFRSVUUsIGV2YWwgPSBGQUxTRX0NCnN0YXRlc19zaHAgPC0gc3RhdGVzKCkgICU+JQ0KICBzdF9hc19zZigpICU+JQ0KICBzdF90cmFuc2Zvcm0oY3JzID0gNDMyNikNCg0KcGFfc2hwIDwtIHN0YXRlc19zaHAgJT4lDQogIGZpbHRlcihTVFVTUFMgPT0gIlBBIikNCg0Kbmpfc2hwIDwtIHN0YXRlc19zaHAgJT4lDQogIGZpbHRlcihTVFVTUFMgPT0gIk5KIikNCg0KZGVfc2hwIDwtIHN0YXRlc19zaHAgJT4lDQogIGZpbHRlcihTVFVTUFMgPT0gIkRFIikNCmBgYA0KDQojIDMuIFByZWN1cnNvciBSb3V0aW5lIC0gQmFzZWxpbmUgUEEgUmV0YWlsZXIgRGF0YQ0KDQpPdXIgYmFzZWxpbmUgUEEgZGF0YSBjb21lIGZyb20gYSBzaGFwZWZpbGUgZG93bmxvYWRlZCBieSB0aGUgR2Vvc2Nhbm5pbmcgdGVhbSBpbiAyMDE4IGZyb20gdGhlIENvbW1vbndlYWx0aCdzIE9wZW4gRGF0YSBwb3J0YWwuIFRoaXMgaXMgYSBwcmVjdXJzb3IgdG8gb3VyIGNvZGUgcm91dGluZSAoZS5nLiBvdXIgY29kZSB0YWtlcyBpbiBORVcgZGF0YSkuIFRoZSBmb2xsb3dpbmcgc3RlcHMgZG8gbm90IG5lZWQgdG8gYmUgcmUtcnVuLCBhbmQgYWN0IG9ubHkgYXMgZG9jdW1lbnRhdGlvbiBmb3IgdGhlIGJ1aWxkaW5nIG9mIHRoZSBwb3J0aW9uIG9mIG91ciByZXRhaWxlciBkYXRhYmFzZSAxMS8yMDE4LTEvMjAyMC4gQW5jZXN0cmFsIGRhdGEgYXJlIG5vdCBwdWJsaXNoZWQgYnkgUEEsIHRoaXMgcHJvdmlkZXMgYSBmYWlybHkgZ29vZCBsb29rIGludG8gdGhlIHJlY2VudCBwYXN0Lg0KDQojIyAzLjEuIFJlYWQgbmV3IGRhdGEgZnJvbSBTb2NyYXRhDQoNClN0ZXBzOg0KDQoxLiBHcmFiIHRoZSBkYXRhIGZyb20gdGhlIFBBIFNPREEgQVBJIC0gYXQgdGhlIHRpbWUgKDEvMjkvMjAyMCkgdGhlIG1vc3QgcmVjZW50IHNldC4gKGByZWFkLnNvY3JhdGFgKQ0KDQoyLiBGaWx0ZXIgb3V0IG9mIHN0YXRlIGFuZCB2ZW5kaW5nIG9ic2VydmF0aW9ucw0KDQozLiBBZGQgYSB0aW1lIHN0YW1wIHdpdGggdGhlIGRhdGUgb2YgZG93bmxvYWQgKDEvMjkvMjAyMCksIGEgY29sdW1uIGZvciAnc3RhdGUnIGFuZCBjb2VyY2UgdGhlIGxpY2Vuc2UgZXhwaXJhdGlvbiBkYXRlIHRvIGRhdGUgKCd5bWQnKQ0KDQo0LiBSZW5hbWUgcmVsZXZhbnQgY29sdW1ucyB0byBsYXQsIGxvbiBhbmQgYWRkcmVzc19mdWxsDQoNCjUuIFVzZSBgbXV0YXRlX2lmYCB0byBjb2VyY2UgYWxsIGZhY3RvcnMgdG8gY2hhcmFjdGVyDQoNCjYuIFJlcGxhY2UgIiYiIGNoYXJhY3RlcnMgd2l0aCAiQU5EIiAoYmV0dGVyIGZvciBnZW9jb2RpbmcpDQoNCkFkZHJlc3MgZGF0YSBhcmUgaW4gYSBqc29uLWxpa2UgZm9ybWF0IChpbiBicmFja2V0cyBldGMpIGJ1dCB0aGlzIHNob3VsZCBub3QgYm90aGVyIHRoZSBHb29nbGUgZ2VvY29kZXIuDQoNCmBgYHtyIHJlYWRfU29jcmF0YV9wYSwgY2FjaGUgPSBUUlVFLCBldmFsID0gRkFMU0V9DQpyZXRhaWxlcnNfU29jcmF0YSA8LSByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5wYS5nb3YvcmVzb3VyY2UvdXQ3Mi1zZnQ4Lmpzb24iKSAlPiUNCiAgZmlsdGVyKGNvdW50eSAhPSAiVU5LTk9XTi9PVVQgT0YgU1RBVEUiLA0KICAgICAgICAgbGljZW5zZV90eXBlICE9ICJWZW5kaW5nIikgJT4lDQogIG11dGF0ZShwdWJsaXNoX2RhdGUgPSB0b2RheSh0em9uZSA9ICJBbWVyaWNhL05ld19Zb3JrIiksDQogICAgICAgICBzdGF0ZSA9ICJQQSIsDQogICAgICAgICBleHBpcmF0aW9uX2RhdGUgPSB5bWQoZXhwaXJhdGlvbl9kYXRlKSkgJT4lDQogIHJlbmFtZShsYXQgPSBsb2NhdGlvbl8xLmxhdGl0dWRlLA0KICAgICAgICAgbG9uID0gbG9jYXRpb25fMS5sb25naXR1ZGUsDQogICAgICAgICBhZGRyZXNzX2Z1bGwgPSBsb2NhdGlvbl8xLmh1bWFuX2FkZHJlc3MpICU+JQ0KICBkcGx5cjo6c2VsZWN0KC1sZWdhbF9uYW1lLCAtcG9zdGFsX2NvZGUsIC1jb3VudHJ5KSAlPiUNCiAgbXV0YXRlX2lmKGlzLmZhY3RvciwgYXMuY2hhcmFjdGVyKSAlPiUNCiAgbXV0YXRlKGFkZHJlc3NfZnVsbCA9IHN0cl9yZXBsYWNlKGFkZHJlc3NfZnVsbCwgIiYiLCAiQU5EIikpDQpgYGANCg0KIyMgMy4yLiBSZWFkIGluIG9yaWdpbmFsIHNoYXBlZmlsZSBmcm9tIDIwMTgNCg0KVGhlIG9sZGVyLCBzaGFwZWZpbGUgZGF0YSwgYXJlIG5vdCB1bmlmb3JtIHdpdGggdGhlIGRhdGEgZnJvbSB0aGUgU09EQSBBUEkuIFdlIHVuaXRlIHRoZSBhZGRyZXNzIGluZm9ybWF0aW9uLCByZW5hbWUgdmFyaW91cyBjb2x1bW5zIHRvIGNvbXBvcnQgd2l0aCBuZXdlciBkYXRhLCBleHRyYWN0IGxhdC9sb24gdmFsdWVzIGZyb20gdGhlIGdlb21ldHJ5LCBjb2VyY2UgdG8gZGF0YSBmcmFtZSwgcmVtb3ZlIGdlb21ldGVyeSwgY29udmVydCBmYWN0b3JzIHRvIGNoYXJhY3RlciAoYG11dGF0ZV9pZmApLg0KDQpUaGlzIGNvZGUgcHJvYmFibHkgd29uJ3QgYmUgbmVjZXNzYXJ5IHRvIHVzZSBpbiB0aGUgZnV0dXJlIHdoZW4geW91IGFyZSB1c2luZyBkb3dubG9hZHMgZnJvbSB0aGUgU29jcmF0YSBTT0RBIEFQSS4NCg0KYGBge3IgbG9hZF9zaGFwZWZpbGUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT0gRkFMU0UsY2FjaGU9VFJVRSwgZXZhbCA9IEZBTFNFfQ0KcmV0YWlsZXJzXzEgPC0gc3RfcmVhZCgiLy9qb3ZlLmRlc2lnbi51cGVubi5lZHUvRGVwdC1TaGFyZXMvcHJheC8wMSBQcm9qZWN0IEZvbGRlcnMvMjAxOV9Bbm5lbmJlcmdfR2VvU2Nhbm5pbmcvaW5wdXREYXRhL1JldGFpbGVyTWFwcy9SZXRhaWxlck1hcHMvUEEvMjAxOF8xMS9yb3dzXzExL3Jvd3NfMTEuc2hwIikgJT4lDQogIGZpbHRlcihsb2NhdGlvbl8zID09ICJQQSIsDQogICAgICAgICBjb3VudHkgIT0gIlVOS05PV04vT1VUIE9GIFNUQVRFIiwNCiAgICAgICAgIGxpY2Vuc2VfdHkgIT0gIlZlbmRpbmciKSAlPiUNCiAgbXV0YXRlKGxvY2F0aW9uXzEgPSBhcy5jaGFyYWN0ZXIobG9jYXRpb25fMSksIA0KICAgICAgICAgbG9jYXRpb25fMiA9IGFzLmNoYXJhY3Rlcihsb2NhdGlvbl8yKSwgDQogICAgICAgICBsb2NhdGlvbl8zID0gYXMuY2hhcmFjdGVyKGxvY2F0aW9uXzMpLCANCiAgICAgICAgIHBvc3RhbF9jb2QgPSBhcy5jaGFyYWN0ZXIocG9zdGFsX2NvZCksDQogICAgICAgICBzdGF0ZSA9ICJQQSIpICU+JSANCiAgdW5pdGUoLiwgImFkZHJlc3NfZnVsbCIsIHNlcCA9ICIgIiwNCiAgICAgICAgYygibG9jYXRpb25fMSIsICJsb2NhdGlvbl8yIiwgImxvY2F0aW9uXzMiLCAicG9zdGFsX2NvZCIpKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtY29tcHV0ZWRfciwgLXZlcnNpb24sIC1jb21wdXRlZF8xLCAtY29tcHV0ZWRfMiwgLWNvbXB1dGVkXzMsIC1jb21wdXRlZF80LA0KICAgICAgICAgLWNhcnRvZGJfaWQsIC1jb3VudHJ5LCAtbG9jYXRpb25fNCwgLWNyZWF0ZWRfYXQsIC1pZCwgLWNvdW50cnksIC1sZWdhbF9uYW1lKSAlPiUNCiAgcmVuYW1lKHB1Ymxpc2hfZGF0ZSA9IHVwZGF0ZWRfYXQsDQogICAgICAgICBleHBpcmF0aW9uX2RhdGUgPSBleHBpcmF0aW9uLA0KICAgICAgICAgbGljZW5zZV90eXBlID0gbGljZW5zZV90eSkgJT4lDQogIG11dGF0ZShsb249bWFwX2RibChnZW9tZXRyeSwgfnN0X2NlbnRyb2lkKC54KVtbMV1dKSwNCiAgICAgICAgIGxhdD1tYXBfZGJsKGdlb21ldHJ5LCB+c3RfY2VudHJvaWQoLngpW1syXV0pKSU+JQ0KICAgICAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICAgICAgICAgICAgICAgICAgc2VsZWN0KC1nZW9tZXRyeSkgJT4lDQogIG11dGF0ZV9pZihpcy5mYWN0b3IsIGFzLmNoYXJhY3RlcikgICU+JSANCiAgbXV0YXRlKGFkZHJlc3NfZnVsbCA9IHN0cl9yZXBsYWNlKGFkZHJlc3NfZnVsbCwgIiYiLCAiQU5EIikpDQogIA0KYGBgDQoNCkNoZWNrIHRvIHNlZSBob3cgbWFueSBOQSBpbiBzcGF0aWFsIGluZm9ybWF0aW9uIGJ5IHN1bW1hcml6aW5nLg0KDQpgYGB7ciByZXRhaWxlcl9uYV9zdW1tYXJ5X3NvY3JhdGEsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT0gRkFMU0UsY2FjaGU9VFJVRSwgZXZhbCA9IEZBTFNFfQ0Kc3VtbWFyeShpcy5uYShyZXRhaWxlcnNfU29jcmF0YSRsYXQpKQ0Kc3VtbWFyeShpcy5uYShyZXRhaWxlcnNfMSRsYXQpKQ0KYGBgDQoNCiMjIDMuMyAgQ29hbGVzY2UgRGF0YSBTZXRzDQoNCkNoZWNrIHRvIHNlZSB3aGljaCBsaWNlbnNlcyBhcmUgbmV3IGluIHRoZSBpbmNvbWluZyBkYXRhLiBXaGVuIHdlIGZpbmQgbmV3IGVudHJpZXMgdGhhdCBtYXRjaCBlbnRyaWVzIGluIG91ciBleGlzdGluZyBkYXRhLCB3ZSB1cGRhdGUgdGhlaXIgZGF0ZSBvZiBleHBpcnkuIElmIGRhdGEgZW50cmllcyBpbiB0aGUgb2xkIGRhdGFiYXNlIGFyZSBub3QgcmVwcmVzZW50ZWQgaW4gdGhlIG5ldyBvbmUsIHdlIG1hcmsgdGhlbSBhcyBleHBpcmVkLg0KDQpOb3RlIHRoZSB1c2Ugb2YgdGhlIGBkcGx5cmAgdmVyc2lvbiBvZiBgaWZlbHNlYCBjYWxsZWQgYGlmX2Vsc2VgIGhlcmUgaW4gb3JkZXIgdG8gbWFrZSBzdXJlIHRoZSByZXN1bHQgaXMgYSBkYXRlIGFuZCBub3QgbnVtZXJpYy4NCg0KSGVyZSdzIHdoYXQgdGhpcyBkb2VzOg0KDQoxLiAgRnVsbCBKb2luIG9mIHJldGFpbGVycyBmcm9tIE5vdiAyMDE4IChlLmcuIGV4aXN0aW5nIHJldGFpbGVycykgdG8gYHJldGFpbGVyc19Tb2NyYXRhYCAoZS5nLiBuZXcgcmV0YWlsZXJzKSBqb2luZWQgYnkgYm90aCBhY2NvdW50IGFuZCB0cmFkZSBuYW1lLiBTaW5jZSB0aGUgZGF0YSBzZXRzIGhhdmUgaWRlbnRpY2FsIG5hbWVzLCB0aGlzIHByb2R1Y2VzIGFuICJ4IiBhbmQgInkiIGNvbHVtbiBmb3IgZWFjaCBvZiBvdXIgY2Fub25pY2FsIG5hbWVzIC0geCBiZWluZyB0aGUgbGVmdCBzaWRlIGFuZCB5IGJlaW5nIHRoZSByaWdodCBzaWRlIG9mIHRoZSBqb2luLg0KDQoyLiAgSWYgdGhlcmUgaXMgbm8gZXhwaXJhdGlvbiBkYXRlIG9uIGZpbGUgZm9yIHRoZSBTb2NyYXRhIHNldCAoZS5nLiB0aGVyZSBpcyBubyBlbnRyeSBmb3IgYSByZXRhaWxlciBvbiBmaWxlIGluIHRoZSBuZXdlc3QgZGF0YSBzZXQpLCBhc3N1bWUgdGhlIGFjY291bnQgaXMgZXhwaXJlZC4gQ3JlYXRlIGEgY29sdW1uIGBleHBpcmVkX3lfbmAgYW5kIG1hcmsgaXQgIkVYUElSRUQiIG9yIGVsc2UgIkFDVElWRSINCg0KMy4gSWYgaXQncyBleHBpcmVkIChlLmcuIHRoZXJlIGlzIG5vIGNvcnJlc3BvbmRpbmcgeSBlbnRyeSBmb3IgYSBnaXZlbiB4IGVudHJ5KSwgbWFyayBhbiBleHBpcmF0aW9uIGRhdGUgYXMgYmVpbmcgdGhlIGxhc3QgYWN0aXZlIGRhdGUgb2YgdGhlIGxpY2Vuc2Ugb24gZmlsZSBmcm9tIHRoZSBleGlzdGluZyBkYXRhYmFzZS4NCg0KNC4gQ3JlYXRlIGEgbmV3IHNldCBvZiBjYW5vbmljYWwgY29sdW1ucyBieSBjaGVja2luZyB0byBzZWUgaWYgYm90aCB4IGFuZCB5IGNvbHVtbnMgYXJlIGZpbGxlZCBmb3IgZWFjaCBvZiBvdXIgdmFyaWFibGVzIC0gaWYgdGhlcmUncyBubyB4LCBwdXQgdGhlIHkgZGF0YSBpbiB0aGVyZSBhbmQgdmljZSB2ZXJzYS4NCg0KNS4gR2VuZXJhdGUgbGF0L2xvbiBieSBjaGVja2luZyBpZiBlaXRoZXIgbmV3IG9yIGV4aXN0aW5nIGhhcyBtaXNzaW5nIGxhdC9sb24gaW5mbywgaWYgc28sIHRha2UgdGhlIGV4aXN0aW5nIG9uZSwgaWYgbm90LCB0YWtlIHRoZSBuZXdlc3QgbGF0L2xvbi4NCg0KNi4gVXNlIHNpbWlsYXIgbG9naWMgdG8gZmlsbCBpbiBhZGRyZXNzLCBjb3VudHksIHB1Ymxpc2ggZGF0ZSBhbmQgbGljZW5zZSB0eXBlIGRhdGEuDQoNCjcuIFJlbW92ZSB0aGUgY29sdW1ucyB3aGljaCBhcmUgbGVmdCBvdmVyIGZyb20gdGhlIGpvaW4gKGUuZy4gYGxhdC54YCwgYGxpY2Vuc2VfdHlwZS55YCkgdXNpbmcgYGRwbHlyOjpzZWxlY3RgDQoNCkluIHRoaXMgam9pbiAqdGhlIG1vc3QgcmVjZW50IGRhdGEgc2V0IGdvZXMgc2Vjb25kIChlLmcuIHRoZSAieSIpIGRhdGEgc2V0LioNCg0KYGBge3IgdGVzdF9qb2luLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9IEZBTFNFLGNhY2hlPVRSVUUsIGV2YWwgPSBGQUxTRX0NCnBhX2pvaW4gPC0gZnVsbF9qb2luKHJldGFpbGVyc18xLCANCiAgICAgICAgICAgICAgICAgICAgIHJldGFpbGVyc19Tb2NyYXRhLCBieSA9IGMoImFjY291bnQiLCAidHJhZGVfbmFtZSIpKSAlPiUNCiAgbXV0YXRlKGV4cGlyZWRfeV9uID0gaWZlbHNlKGlzLm5hKGV4cGlyYXRpb25fZGF0ZS55KSA9PSBUUlVFLCAiRVhQSVJFRCIsICJBQ1RJVkUiKSwNCiAgICAgICAgIGV4cGlyYXRpb25fZGF0ZSA9IGlmX2Vsc2UoZXhwaXJlZF95X24gPT0gJ0VYUElSRUQnLCBleHBpcmF0aW9uX2RhdGUueCwgeW1kKGV4cGlyYXRpb25fZGF0ZS55KSksDQogICAgICAgICBsYXQgPSBpZmVsc2UoaXMubmEobGF0LngpID09IEZBTFNFLCBsYXQueCwgbGF0LnkpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGlzLm5hKGxvbi54KSA9PSBGQUxTRSwgbG9uLngsIGxvbi55KSwNCiAgICAgICAgIGFkZHJlc3NfZnVsbCA9IGlmZWxzZShpcy5uYShhZGRyZXNzX2Z1bGwueCkgPT0gRkFMU0UsIGFkZHJlc3NfZnVsbC54LCBhZGRyZXNzX2Z1bGwueSksDQogICAgICAgICBjb3VudHkgPSBpZmVsc2UoaXMubmEoY291bnR5LnkpID09IFRSVUUsIGNvdW50eS54LCBjb3VudHkueSksDQogICAgICAgICBwdWJsaXNoX2RhdGUgPSBpZl9lbHNlKGlzLm5hKHB1Ymxpc2hfZGF0ZS55KSA9PSBUUlVFLCBwdWJsaXNoX2RhdGUueCwgcHVibGlzaF9kYXRlLnkpLA0KICAgICAgICAgbGljZW5zZV90eXBlID0gaWZlbHNlKGlzLm5hKGxpY2Vuc2VfdHlwZS55KSA9PSBUUlVFLCBsaWNlbnNlX3R5cGUueCwgbGljZW5zZV90eXBlLnkpLA0KICAgICAgICAgc3RhdGUgPSAiUEEiKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtbGF0LngsIC1sb24ueCwgLWxhdC55LCAtbG9uLnksIA0KICAgICAgICAgLWFkZHJlc3NfZnVsbC54LCAtYWRkcmVzc19mdWxsLnksIC1jb3VudHkueCwgLWNvdW50eS55LA0KICAgICAgICAgLXB1Ymxpc2hfZGF0ZS54LCAtcHVibGlzaF9kYXRlLnksIC1zdGF0ZS54LCAtc3RhdGUueSwgLWxpY2Vuc2VfdHlwZS54LCAtbGljZW5zZV90eXBlLnksDQogICAgICAgICAtZXhwaXJhdGlvbl9kYXRlLngsIC1leHBpcmF0aW9uX2RhdGUueSkNCmBgYA0KDQpXZSBhZ2FpbiBzdW1tYXJpemUgdG8gY2hlY2sgb24gdGhlIGFtb3VudCBvZiBOQSBkYXRhLg0KDQpgYGB7ciByZXRhaWxlcl9uYV9zdW1tYXJ5X2pvaW4yLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9IEZBTFNFLGNhY2hlPVRSVUUsIGV2YWwgPSBGQUxTRX0NCnN1bW1hcnkoaXMubmEocGFfam9pbiRsYXQpKQ0KYGBgDQoNCiMjIDMuNC4gR2VvY29kZSBtaXNzaW5nIGluZm9ybWF0aW9uDQoNClRoZSBnb29nbGUgZ2VvY29kZXIgdGFrZXMgYSBsaXN0IG9mIGFkZHJlc3NlcyBhbmQgcmV0dXJucyBhIGxpc3Qgb2YgbGF0L2xvbiBwYWlycy4gDQoNCjEuIFdlIGNyZWF0ZSBhIGRhdGFmcmFtZSBvZiBvYnNlcnZhdGlvbnMgdG8gYmUgZ2VvY29kZWQgKGB0b19nZW9jb2RlYCkgd2hpY2ggbGFjayBsYXQvbG9uIGRhdGEuIA0KDQoyLiBXZSBzZW5kIHRoYXQgZGF0YSB0byB0aGUgZ2VvY29kZXIgdmlhIGBnZ21hcGAgKGBnZW9jb2RlYCkuDQoNCjMuIFdlIGBjYmluZGAgdGhlIHJldHVybmVkIGxhdC9sb24gcGFpcnMgdG8gdGhlIHJlc3Qgb2YgdGhlIGNvbHVtbnMgaW4gdGhlIGB0b19nZW9jb2RlYCBkYXRhZnJhbWUNCg0KNC4gV2UgYHJiaW5kYCB0aGUgcm93cyBmcm9tIG91ciBgdG9fZ2VvY29kZWAgc2V0LCB3aGljaCBub3cgaGF2ZSB4LXkgaW5mbywgdG8gb3VyIGRhdGEgd2hpY2ggZGlkIG5vdCBuZWVkIHRvIGJlIGdlb2NvZGVkIGluIHRoZSBmaXJzdCBwbGFjZSBhbmQgY3JlYXRlIGEgZGF0YWZyYW1lIGBwYV9nZW9jb2RlZGAuDQoNCjUuIFBlcmZvcm0gYSBkYXRhIGNoZWNrIHRvIHNlZSB3aGF0IG1pZ2h0IHN0aWxsIGJlIG1pc3NpbmcNCg0KYGBge3IgcmV0YWlsZXJfbmFfc3VtbWFyeV9qb2luLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9IEZBTFNFLGNhY2hlPVRSVUUsIGV2YWwgPSBGQUxTRX0NCnRvX2dlb2NvZGUgPC0gcGFfam9pbiAlPiUNCiAgZmlsdGVyKGlzLm5hKGxhdCkgPT0gVFJVRSkNCg0KcGFfZ2VvY29kZWQgPC0gZ2VvY29kZSh0b19nZW9jb2RlJGFkZHJlc3NfZnVsbCwgc291cmNlID0gICJnb29nbGUiKSAlPiUNCiAgY2JpbmQoLiwgdG9fZ2VvY29kZSAlPiUNCiAgICAgICAgICBzZWxlY3QoLWxhdCwgLWxvbikpICU+JQ0KICByYmluZChwYV9qb2luICU+JQ0KICAgICAgICAgIGZpbHRlcihpcy5uYShsYXQpID09IEZBTFNFKSwgLikNCg0Kc3VtbWFyeShpcy5uYShwYV9nZW9jb2RlZCRsYXQpKQ0KYGBgDQoNCiMjIDMuNS4gUGVyZm9ybSBxdWFsaXR5IGNoZWNrIGZvciBiYWQgZ2VvY29kZXMNCg0KV2hlbiBnZW9jb2RlcyBhcmUgd3JvbmcsIHRoZXkgYXJlIG9mdGVuIHZlcnkgd3JvbmcsIGFuZCBzZXQgc29tZXdoZXJlIHdpbGRseSBkaWZmZXJlbnQgZnJvbSBvdXIgdGFyZ2V0IGFyZWEuIFdlIGRvIGEgc3BhdGlhbCBqb2luIHdpdGggdGhlIFBBIHNoYXBlZmlsZSwga2VlcCBvbmx5IHRoZSBvYnNlcnZhdGlvbnMgd2hpY2ggZmFsbCBvdXRzaWRlIG9mIFBBLiBXZSBjYW4gY2hlY2sgdGhlc2Ugb2JzZXJ2YXRpb25zIGFuZCBwb3RlbnRpYWxseSBnZW9jb2RlIHRoZW0gbWFudWFsbHkuDQoNCldlIGRvIHRoaXMgdGhyb3VnaCB0aGUgZm9sbG93aW5nIHN0ZXBzOg0KDQoxLiBQZXJmb3JtIGEgc3BhdGlhbCBqb2luIHVzaW5nIGBzdF9qb2luYCBmcm9tIHRoZSBgc2ZgIHBhY2thZ2UgdG8gc2VlIHdoaWNoIHBvaW50cyBmcm9tIGBwYV9nZW9jb2RlZGAgZmFsbCBvdXRzaWRlIFBBIChleGNsdWRpbmcgdGhvc2Ugd2Uga25vdyBhcmUgTkEgYXQgcHJlc2VudCkuIFRvIGRvIHRoaXMgd2UgbmVlZCB0byByZXByb2plY3Qgb3VyIGxhdC9sb24gZGF0YSBhcyBhbiBgc2ZgIHZlY3RvciBvYmplY3QgcHJvamVjdGVkIGluIFdHUzg0ICh3ZWIgbWVyY2F0b3IsIGBjcnMgPSA0MzI2YCkgYW5kIGpvaW4gaXQgdG8gYHBhX3NocGAuDQoNCjIuIFdlIGBmaWx0ZXJgIHRob3NlIG9ic2VydmF0aW9ucyB3aGljaCBjb21lIGJhY2sgYXMgYGlzLm5hKFNUVVNQUykgPT0gVFJVRWAgKGUuZy4gdGhleSBkb24ndCBqb2luIGF0IGFsbCB0byB0aGUgUEEgdGlncmlzIHNoYXBlZmlsZSBhbmQgZG9uJ3QgdGFrZSBvbiBhbnkgb2YgaXQncyBhdHRyaWJ1dGVzIGxpa2UgU1RVU1BTKSBhbmQga25vdyB0aGF0IHRoZXNlIGFyZSB0aGUgb25lcyB3aGljaCBmZWxsIG91dHNpZGUgb3VyIHNoYXBlZmlsZS4NCg0KMy4gV2UgYGRwbHlyOjpzZWxlY3RgIG9ubHkgb3VyIGNhbm9uaWNhbCB2YXJpYWJsZXMgZnJvbSB0aGVzZSBhbmQgZXh0cmFjdCB0aGUgbGF0L2xvbiBmcm9tIHRoZWlyIGdlb21ldHJpZXMgdXNpbmcgYSBgbXV0YXRlYCBzdGF0ZW1lbnQuIFdlIHdhbnQgdG8gdHVybiB0aGlzIGJhY2sgaW50byBhIGRhdGFmcmFtZSB3aXRoIGxhdC9sb24gZGF0YSBhbmQgbm90IGdlb21ldHJpZXMgbGlrZSBhbiBgc2ZgIG9iamVjdC4gU28gd2Ugc2V0IGl0IGBhcy5kYXRhLmZyYW1lYCBhbmQgcmVtb3ZlIHRoZSBgZ2VvbWV0cnlgIGNvbHVtbi4NCg0KNC4gV2Ugbm93IGByYmluZGAgdGhlc2UgZGF0YSBiYWNrIHRvIG91ciBgcGFfZ2VvY29kZWRgIG9ic2VydmF0aW9ucyB3aGljaCBsYWNrZWQgbGF0L2xvbiBpbiB0aGUgZmlyc3QgcGxhY2UsIHRvIGNyZWF0ZSBhIGRhdGEgc2V0IGNvbnNpc3Rpbmcgb2YgYWxsIG91ciBvYnNlcnZhdGlvbnMgd2hpY2ggd2UgcHJlc3VtZSBoYXZlIGJhZCBnZW9jb2Rlcy4NCg0KYGBge3IgZGV0ZWN0X2Vycm9ycywgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0Kb3V0c2lkZV9QQSA8LSBzdF9qb2luKHBhX2dlb2NvZGVkICU+JSANCiAgICAgICAgICBmaWx0ZXIoaXMubmEobGF0KSA9PSBGQUxTRSkgJT4lIA0KICAgICAgICAgIHN0X2FzX3NmKC4sIGNvb3JkcyA9IGMoImxvbiIsICJsYXQiKSwgY3JzID0gNDMyNiksIA0KICAgICAgICBwYV9zaHAsIA0KICAgICAgICBqb2luID0gc3Rfd2l0aGluLCANCiAgICAgICAgbGVmdCA9IFRSVUUpICU+JQ0KICBmaWx0ZXIoaXMubmEoU1RVU1BTKSA9PSBUUlVFKSAlPiUNCiAgZHBseXI6OnNlbGVjdCh0cmFkZV9uYW1lLCBhY2NvdW50LCBhZGRyZXNzX2Z1bGwsIGNvdW50eSkgJT4lDQogIG11dGF0ZShsb249bWFwX2RibChnZW9tZXRyeSwgfnN0X2NlbnRyb2lkKC54KVtbMV1dKSwNCiAgICAgICAgIGxhdD1tYXBfZGJsKGdlb21ldHJ5LCB+c3RfY2VudHJvaWQoLngpW1syXV0pKSU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLWdlb21ldHJ5KSAlPiUNCiAgcmJpbmQoLiwgcGFfZ2VvY29kZWQgJT4lDQogICAgICAgICAgZmlsdGVyKGlzLm5hKGxhdCkgPT0gVFJVRSkpDQpgYGANCg0KDQojIyAzLjYuIE1hbnVhbGx5IGdlb2NvZGUgcmVtYWluaW5nIG9ic2VydmF0aW9ucw0KDQpUaGUgb2JzZXJ2YXRpb25zIHRoYXQgZG9uJ3QgZ2VvY29kZSB3aWxsIG5lZWQgdG8gYmUgZGVhbHQgd2l0aCBtYW51YWxseSAoZS5nLiBpbnB1dCBsYXQvbG9uIGNvb3JkaW5hdGVzIGJhc2VkIG9uIGdvb2dsZSBtYXAgcmVzZWFyY2gpLiBTaW1pbGFybHksIG9ic2VydmF0aW9ucyB0aGF0IGdlb2NvZGUgcG9vcmx5IGFuZCBlbmQgdXAgb24gb3VyIGBvdXRzaWRlX1BBYCBsaXN0IG5lZWQgdG8gYmUgbWFudWFsbHkgZ2VvY29kZWQuDQoNClRoaXMgaGFzIHRvIGJlIGRvbmUgYnkgaGFuZCB1bmZvcnR1YXRlbHkuIFlvdSBjYW4gZ28gdGhyb3VnaCB0aGUgb2JzZXJ2YXRpb25zIHdoaWNoIGVpdGhlciBoYXZlIE5BIHZhbHVlcyBmb3IgbGF0L2xvbiBpbiBgcGFfZ2VvY29kZWRgIG9yIGFyZSBmb3VuZCB0byBiZSBvdXRzaWRlIFBBIGluIHRoZSBgb3V0c2lkZV9QQWAgZGF0YWZyYW1lLiBIZXJlIHdlIGRvIHRoaXMgd2l0aCB3ZWIgcmVzZWFyY2ggYW5kIGBtdXRhdGVgIHN0YXRlbWVudHMgd2hlcmUgd2UgcmVwbGFjZSB0aGUgbGF0L2xvbiB2YWx1ZXMgZm9yIGluZGl2aWR1YWwgbGljZW5zZXMuDQoNCkluIG90aGVyIGRhdGEgc2V0cyBhbmQgbGF0ZXIgaW4gdGhpcyBjb2RlIHdlIGV4cG9ydCB0aGUgZGF0YSB0byBhIGNzdiBhbmQgYW4gUkEgZG9lcyB0aGUgY29kaW5nLCB3aGljaCB3ZSB0aGVuIHJlLWltcG9ydCBhbmQgaW50ZWdyYXRlLiBUaGUgZXhwb3J0IGFwcHJvYWNoIGlzIHByb2JhYmx5IGxlc3MgcHJvbmUgdG8gZXJyb3IgZnJvbSB0cmFuc2NyaXB0aW9uIHByb2JsZW1zLg0KDQpgYGB7ciBmaXhfbWlzc2luZ19pbmZvMiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPSBGQUxTRSxjYWNoZT1UUlVFLCBldmFsID0gRkFMU0V9DQpwYV9nZW9jb2RlZF9maXhlZCA8LSBwYV9nZW9jb2RlZCAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICI1MSoqNjc3OCIsIDM5Ljk0ODk4OSwgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICI1MSoqNjc3OCIsIC03NS4xNDc1NjgsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjMwKioxNzg5IiwgMzkuOTMxNTY3LCBsYXQpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjMwKioxNzg5IiwgLTgwLjA1ODAyMiwgbG9uKSkgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiNzAqKjIwMDQiLCA0MS4zNDE5MzUsIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiNzAqKjIwMDQiLCAtNzUuOTkyNDA5LCBsb24pKSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICI1MSoqODQ0NSIsIDM5Ljk1NDk4MywgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICI1MSoqODQ0NSIsIC03NS4xNjQ2MTYsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjcwKioxMDA1IiwgNDEuMDE3ODAzLCBsYXQpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjcwKioxMDA1IiwgLTc1LjU4OTYzNiwgbG9uKSkgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiNTEqKjM0NDgiLCAzOS45NTA5MjMsIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiNTEqKjM0NDgiLCAtNzUuMTYzODg0LCBsb24pKSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICI2NCoqOTQ0NSIsIDQxLjQ2MzUyNCwgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICI2NCoqOTQ0NSIsIC03NS4xODA4MDksIGxvbikpJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiMDYqKjE2NzUiLCA0MC41NTcyNzEsIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiMDYqKjE2NzUiLCAtNzUuOTc3NjMyLCBsb24pKSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICIwOSoqNzU5MyIsIDQwLjEwOTI1MCwgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICIwOSoqNzU5MyIsIC03NC44NTU3MzYsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjQ1Kio0MTQyIiwgNDEuMDk0OTEwLCBsYXQpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjQ1Kio0MTQyIiwgLTc1LjMyNzcwNywgbG9uKSkgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiMTAqKjc5NzQiLCA0MS4wNzEwOTQsIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiMTAqKjc5NzQiLCAtODAuMDQ1NDM0LCBsb24pKSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICIxNyoqNjc0NiIsIDQwLjk5OTkzNywgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICIxNyoqNjc0NiIsIC03OC4zNDc0MjgsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjM0Kio4MzAwIiwgNDAuOTk5OTM3LCBsYXQpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjM0Kio4MzAwIiwgLTc4LjM0NzQyOCwgbG9uKSkgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiNDkqKjEyNzEiLCA0MC42ODcwMDAsICBsYXQpLCANCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICI0OSoqMTI3MSIsIC03Ni44Mjk2MTAsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjA0Kio1NzIxIiwgNDAuNTg2MDI1LCAgIGxhdCksIA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjA0Kio1NzIxIiwgLTgwLjI1NzgwNywgbG9uKSkgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiNTEqKjA0OTkiLCAzOS45NDgzMDMsICAgIGxhdCksIA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjUxKiowNDk5IiwgNDIuMTE1OTkwLCAgbG9uKSkgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiNzAqKjUxNTciLCAtODAuMDMzMDg3LCAgICBsYXQpLCANCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICI3MCoqNTE1NyIsIC03NS4xNjQ2MzMsIGxvbikpICU+JSANCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICI3MCoqMTAxNCIsIDQwLjk2MDg0NCwgICAgbGF0KSwgDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiNzAqKjEwMTQiLCAtNzcuNzYyMDc0LCBsb24pKSAlPiUgDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiNzAqKjQwMDIiLCA0MS4wMjc1MDMsICAgIGxhdCksIA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjcwKio0MDAyIiwgLTc2LjA3OTk0NiwgbG9uKSkgJT4lIA0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjMyKioxMjQ1IiwgNDAuNjEzODExLCBsYXQpLCANCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICIzMioqMTI0NSIsLTc5LjE4ODU3NSwgbG9uKSkgJT4lIA0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjUxKiozOTYzIiwgMzkuOTUzMjk2LCBsYXQpLCANCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICI1MSoqMzk2MyIsIC03NS4xNzE0OTgsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjQ1Kio3OTY5IiwgNDEuMDczNDU0LCBsYXQpLCANCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICI0NSoqNzk2OSIsIC03NS41NzI1MDAsIGxvbikpICU+JSANCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICI1MSoqNjY3NiIsIDM5Ljk0NzA1OSwgIGxhdCksIA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjUxKio2Njc2IiwgLTc1LjE2NDUzMCwgbG9uKSkgJT4lIA0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjE0Kio4MjIwIiwgNDAuODM0Nzc1LCAgIGxhdCksIA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjE0Kio4MjIwIiwgLTc3LjY3MjQ3OSwgbG9uKSkgJT4lIA0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjIxKiowNzQwIiwgNDAuMjUxNDYyLCBsYXQpLCANCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICIyMSoqMDc0MCIsIC03Ni45MTMwNzAsIGxvbikpICU+JSANCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICI3MCoqMjA3NSIsIDM5Ljk5MDc4NywgIGxhdCksIA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjcwKioyMDc1IiwgLTc1LjYxNTg2NiwgbG9uKSkgJT4lIA0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjE5KioxNjY5IiwgNDEuMDQyMDc5LCAgIGxhdCksIA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjE5KioxNjY5IiwgLTc2LjMwNDEyNCwgbG9uKSkgJT4lIA0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjAzKio0MDMzIiwgNDAuODEzNTgyLCAgICBsYXQpLCANCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICIwMyoqNDAzMyIsIC03OS41NTA0MDYsIGxvbikpICU+JSANCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICI3MCoqODA0MiIsIDQxLjY1NjQzMSwgIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiNzAqKjgwNDIiLCAtODAuNDI0Mzc1LCBsb24pKSAlPiUNCiAgICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgJWluJSBjKCI3MCoqODAxMCIsICI3MCoqODAwOSIsICI3MCoqODAwNiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiNzAqKjgwMDIiLCAiNzAqKjgwMDEiKSwgMzkuODc2MjU1LCAgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ICVpbiUgYygiNzAqKjgwMTAiLCAiNzAqKjgwMDkiLCAiNzAqKjgwMDYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjcwKio4MDAyIiwgIjcwKio4MDAxIiksIC03NS4yNDI5NjIsIGxvbikpICU+JQ0KICAgIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiNzAqKjcwMDkiLCA0MC4yODIwMjcsICBsYXQpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjcwKio3MDA5IiwgLTc5LjU1MDY3NCwgbG9uKSkgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiNzAqKjUxMzAiLCA0MS45OTUxMjAsICAgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICI3MCoqNTEzMCIsIC03Ny4xMjQ4MDQsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgJWluJSBjKCI3MCoqNDEwOSIsICI3MCoqNDA3NCIpLCA0MC40OTI4MjIsIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCAlaW4lIGMoIjcwKio0MTA5IiwgIjcwKio0MDc0IiksIC03Ny4xMTQwODYsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjcwKiozMjAwIiwgNDAuMzY3NjA0LCAgIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiNzAqKjMyMDAiLCAtNzguNDMwODgxLCBsb24pKSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZSh0cmFkZV9uYW1lID09ICJTICYgQiBBU1NPQ0lBVEVTIElOQyIsIDM5Ljk1MDI2OSwgICBsYXQpLCANCiAgICAgICAgIGxvbiA9IGlmZWxzZSh0cmFkZV9uYW1lID09ICJTICYgQiBBU1NPQ0lBVEVTIElOQyIsICAtNzUuMTQ3NDQxLCBsb24pKSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICI2MyoqOTA2NyIsIDQwLjE1OTU5OSwgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICI2MyoqOTA2NyIsIC04MC4yNzYzNTIsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjYzKio5MTY1IiwgNDAuMzY3ODIyLCAgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICI2MyoqOTE2NSIsIC04MC4yOTAzOTgsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjIzKiowNzcyIiwgMzkuOTA0OTQwMSwgIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiMjMqKjA3NzIiLCAtNzUuMzg4NTM0NCwgbG9uKSkgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiNDAqKjA1ODciLCA0MS4zNDc1MjQsICAgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICI0MCoqMDU4NyIsIC03NS45NjA1NTgsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjYyKio4MzkwIiwgNDEuODQxNzQwLCAgICBsYXQpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjYyKio4MzkwIiwgLTc5LjM1NDA4OCwgbG9uKSkgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiMDIqKjUzNzMiLCA0MC41MDQ4NjQsICAgICBsYXQpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjAyKio1MzczIiwgLTc5LjY3NTY0MiwgbG9uKSkgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiNTQqKjkyNTAiLCA0MC42OTA4MjksIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiNTQqKjkyNTAiLCAtNzYuMjYwMDAzLCBsb24pKSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICIxNSoqOTk0OSIsIDM5Ljk4NjIwNCwgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICIxNSoqOTk0OSIsIC03NS44MDU4NjcsIGxvbikpIA0KDQojIEFERCBBRERJVElPTkFMIEdFT0NPRElORyBNVVRBVEVTIEhFUkUNCg0KYGBgDQoNCiMjIDMuNy4gV3JpdGUgb3V0IHRoZSBkYXRhc2V0DQoNCldlIGV4cG9ydCBvdXIgUEEgYmFzZWxpbmUgZGF0YSBzZXQgYXMgYSBjc3YuDQoNCmBgYHtyIGV4cG9ydF9wYTIsIHdhcm5pbmc9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0Kd3JpdGUuY3N2KHBhX2dlb2NvZGVkX2ZpeGVkLCAiLy9qb3ZlLmRlc2lnbi51cGVubi5lZHUvRGVwdC1TaGFyZXMvcHJheC8wMSBQcm9qZWN0IEZvbGRlcnMvMjAxOV9Bbm5lbmJlcmdfR2VvU2Nhbm5pbmcvZGF0YU91dHB1dHMvcGFfZ2VvY29kZWRfZml4ZWRfNV8xNV8yMC5jc3YiKQ0KYGBgDQoNCg0KIyA0LiBQQSBEYXRhIC0gU29jcmF0YSBSb3V0aW5lIGZvciBOZXcgRGF0YQ0KDQoqVGhpcyBpcyB0aGUgZGF0YSByb3V0aW5lIHdlIHdpbGwgdXNlIGZvciB1cGRhdGluZyB0aGUgUEEgZGF0YS4gSXRzIG5hbWluZyBjb252ZW50aW9ucyBjb25mb3JtIHRvIHNpbWlsYXIgcm91dGluZXMgZm9yIE5KIGFuZCBERSBhbmQgaXRzIGRhdGEgb3V0cHV0cyB3aWxsIGludGVncmF0ZSB3aXRoIHRob3NlIG90aGVyIHN0YXRlcy4qIA0KDQpUaGlzIHJvdXRpbmUgaW52b2x2ZXMgZG93bmxvYWRpbmcgbGljZW5zZSBkYXRhIGZyb20gdGhlIFBBIFNPREEgQVBJIGVuZHBvaW50LCAoc28gbG9uZyBhcyB0aGF0IGlzIHRoZSBzdGFuZGFyZCBmb3JtYXQgZm9yIHN1Y2ggZGF0YSkgYW5kIGNyb3NzLWNoZWNraW5nIGl0IGFnYWluc3QgYmFzZWxpbmUgZGF0YSB0byBjbG9zZSBvdXQgb2xkIHJldGFpbGVyIGxvY2F0aW9ucyBhbmQgYWRkIGVudHJpZXMgZm9yIG5ldyBvbmVzLiBPYnNlcnZhdGlvbnMgd2l0aG91dCBhIGdlb2xvY2F0aW9uIGluIHRoZSBzdGF0ZSBkYXRhYmFzZSBhcmUgdGhlbiBnZW9jb2RlZCBhbmQgZXJyb3JzIGFyZSBjaGVja2VkLg0KDQojIyA0LjEuIExvYWQgbmV3IGRhdGEgZnJvbSBTb2NyYXRhDQoNClRoaXMgcm91dGluZSBpcyBiYXNpY2FsbHkgdGhlIHNhbWUgYXMgdGhlIGJhc2VsaW5lIHJvdXRpbmUgZnJvbSBTZWN0aW9uIDMuDQoNClN0ZXBzOg0KDQoxLiBHcmFiIHRoZSBkYXRhIGZyb20gdGhlIFBBIFNPREEgQVBJLiAoYHJlYWQuc29jcmF0YWApDQoNCjIuIEZpbHRlciB0byByZW1vdmUgb3V0IG9mIHN0YXRlIGFuZCB2ZW5kaW5nIG9ic2VydmF0aW9ucw0KDQozLiBBZGQgYSB0aW1lIHN0YW1wIHdpdGggdGhlIGRhdGUgb2YgZG93bmxvYWQsIGEgY29sdW1uIGZvciAnc3RhdGUnIGFuZCBjb2VyY2UgdGhlIGxpY2Vuc2UgZXhwaXJhdGlvbiBkYXRlIHRvIGRhdGUgKCd5bWQnKQ0KDQo0LiBSZW5hbWUgcmVsZXZhbnQgY29sdW1ucyB0byBsYXQsIGxvbiBhbmQgYWRkcmVzc19mdWxsDQoNCjUuIFVzZSBgbXV0YXRlX2lmYCB0byBjb2VyY2UgYWxsIGZhY3RvcnMgdG8gY2hhcmFjdGVyDQoNCjYuIFJlcGxhY2UgIiYiIGNoYXJhY3RlcnMgd2l0aCAiQU5EIiAoYmV0dGVyIGZvciBnZW9jb2RpbmcpDQoNCkFkZHJlc3MgZGF0YSBhcmUgaW4gYSBqc29uLWxpa2UgZm9ybWF0IChpbiBicmFja2V0cyBldGMpIGJ1dCB0aGlzIHNob3VsZCBub3QgYm90aGVyIHRoZSBHb29nbGUgZ2VvY29kZXIuDQoNCmBgYHtyIHJlYWRfU29jcmF0YV9wYTIsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0KcmV0YWlsZXJzX1NvY3JhdGFfcGEgPC0gcmVhZC5zb2NyYXRhKCJodHRwczovL2RhdGEucGEuZ292L3Jlc291cmNlL3V0NzItc2Z0OC5qc29uIikgJT4lDQogIGZpbHRlcihjb3VudHkgIT0gIlVOS05PV04vT1VUIE9GIFNUQVRFIiwNCiAgICAgICAgIGxpY2Vuc2VfdHlwZSAhPSAiVmVuZGluZyIpICU+JQ0KICBtdXRhdGUocHVibGlzaF9kYXRlID0gdG9kYXkodHpvbmUgPSAiQW1lcmljYS9OZXdfWW9yayIpLA0KICAgICAgICAgc3RhdGUgPSAiUEEiLA0KICAgICAgICAgZXhwaXJhdGlvbl9kYXRlID0geW1kKGV4cGlyYXRpb25fZGF0ZSkpICU+JQ0KICByZW5hbWUobGF0ID0gbG9jYXRpb25fMS5sYXRpdHVkZSwNCiAgICAgICAgIGxvbiA9IGxvY2F0aW9uXzEubG9uZ2l0dWRlLA0KICAgICAgICAgYWRkcmVzc19mdWxsID0gbG9jYXRpb25fMS5odW1hbl9hZGRyZXNzKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtbGVnYWxfbmFtZSwgLXBvc3RhbF9jb2RlLCAtY291bnRyeSkgJT4lDQogIG11dGF0ZV9pZihpcy5mYWN0b3IsIGFzLmNoYXJhY3RlcikgJT4lDQogIG11dGF0ZShhZGRyZXNzX2Z1bGwgPSBzdHJfcmVwbGFjZShhZGRyZXNzX2Z1bGwsICImIiwgIkFORCIpKQ0KYGBgDQoNCiMjIDQuMi4gTG9hZCBtb3N0IGN1cnJlbnQgc3RvcmVkIGRhdGEgc2V0DQoNCldlIGxvYWQgdXAgb3VyIHNhdmVkIGJhc2VsaW5lIGRhdGEsIGZyb20gd2hlcmV2ZXIgdGhhdCBtYXkgYmUgc3RvcmVkLiAqV2hlbiBydW5uaW5nIHRoaXMgY29kZSwgdGhlIGZpbGVwYXRoIGluIHRoZSBmb2xsb3dpbmcgY29kZSBjaHVuayB3aWxsIGxpa2VseSBuZWVkIHRvIGJlIGNoYW5nZWQgdG8gcmVmbGVjdCB0aGUgbW9zdCByZWNlbnQgUEEgYmFzZWxpbmUgZGF0YSBzZXQqLg0KDQpOb3RlIHRoYXQgd2UgYGRwbHlyOjpzZWxlY3RgIG9ubHkgb3VyIGBjYW5vbmljYWxfbmFtZXNgIHRvIGFjY291bnQgZm9yIHRoZSBmYWN0IHRoYXQgd2hlbiB3ZSB3cm90ZSB0aGVzZSBkYXRhIG91dCwgdGhleSBsaWtlbHkgd2VyZSBpbXBhcnRlZCB3aXRoIHNvbWUga2luZCBvZiBjb2x1bW4gZGVub3RpbmcgYSByb3cgaWQuIFdlIGRvbid0IHdhbnQgdGhhdC4NCg0KV2UgYWxzbyBtYWtlIHN1cmUgb3VyIGRhdGEgdHlwZXMgYXJlIGNvcnJlY3QgLSB1c2luZyBhIGBtdXRhdGVfaWZgIHN0YXRlbWVudCB0byB0dXJuIGZhY3RvcnMgdG8gY2hhcmFjdGVycyBhbmQgYSBgbXV0YXRlYCBzdGF0ZW1lbnQgdG8gdHVybiBvdXIgZGF0ZXMgaW50byBgZGF0ZWAgZm9ybWF0DQoNCmBgYHtyIGxvYWRfcGFfc2F2ZWQsIGNhY2hlID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIGV2YWwgPSBGQUxTRX0NCnN0b3JlZF9wYSA8LSByZWFkLmNzdigiLy9qb3ZlLmRlc2lnbi51cGVubi5lZHUvRGVwdC1TaGFyZXMvcHJheC8wMSBQcm9qZWN0IEZvbGRlcnMvMjAxOV9Bbm5lbmJlcmdfR2VvU2Nhbm5pbmcvZGF0YU91dHB1dHMvcGFfZ2VvY29kZWRfZml4ZWRfNV8xNV8yMC5jc3YiKSAlPiUNCiAgZHBseXI6OnNlbGVjdChjYW5vbmljYWxfbmFtZXMpICU+JQ0KICBtdXRhdGVfaWYoaXMuZmFjdG9yLCBhcy5jaGFyYWN0ZXIpICU+JQ0KICBtdXRhdGUoZXhwaXJhdGlvbl9kYXRlID0geW1kKGV4cGlyYXRpb25fZGF0ZSksDQogICAgICAgICBwdWJsaXNoX2RhdGUgPSB5bWQocHVibGlzaF9kYXRlKSkNCg0KYGBgDQoNCiMjIDQuMy4gSm9pbiBuZXcgZGF0YSBhbmQgc3RvcmVkIGRhdGENCg0KQ2hlY2sgdG8gc2VlIHdoaWNoIGxpY2Vuc2VzIGFyZSBuZXcgaW4gdGhlIGluY29taW5nIGRhdGEuIFdoZW4gd2UgZmluZCBuZXcgZW50cmllcyB0aGF0IG1hdGNoIGVudHJpZXMgaW4gb3VyIGV4aXN0aW5nIGRhdGEsIHdlIHVwZGF0ZSB0aGVpciBkYXRlIG9mIGV4cGlyeS4gSWYgZGF0YSBlbnRyaWVzIGluIHRoZSBvbGQgZGF0YWJhc2UgYXJlIG5vdCByZXByZXNlbnRlZCBpbiB0aGUgbmV3IG9uZSwgd2UgbWFyayB0aGVtIGFzIGV4cGlyZWQuDQoNCk5vdGUgdGhlIHVzZSBvZiB0aGUgYGRwbHlyYCB2ZXJzaW9uIG9mIGBpZmVsc2VgIGNhbGxlZCBgaWZfZWxzZWAgaGVyZSBpbiBvcmRlciB0byBtYWtlIHN1cmUgdGhlIHJlc3VsdCBpcyBhIGRhdGUgYW5kIG5vdCBudW1lcmljLg0KDQpIZXJlJ3Mgd2hhdCB0aGlzIGRvZXM6DQoNCjEuICBGdWxsIEpvaW4gb2YgcmV0YWlsZXJzIGZyb20gYmFzZWxpbmUgUEEgZGF0YSAoYHN0b3JlZF9wYWApIHRvIG5ldyBkYXRhIChgcmV0YWlsZXJzX1NvY3JhdGFfcGFgKSwgam9pbmVkIGJ5IGJvdGggYWNjb3VudCBhbmQgdHJhZGUgbmFtZS4gU2luY2UgdGhlIGRhdGEgc2V0cyBoYXZlIGlkZW50aWNhbCBuYW1lcywgdGhpcyBwcm9kdWNlcyBhbiAieCIgYW5kICJ5IiBjb2x1bW4gZm9yIGVhY2ggb2Ygb3VyIGNhbm9uaWNhbCBuYW1lcyAtIHggYmVpbmcgdGhlIGxlZnQgc2lkZSBhbmQgeSBiZWluZyB0aGUgcmlnaHQgc2lkZSBvZiB0aGUgam9pbi4NCg0KMi4gIElmIHRoZXJlIGlzIG5vIGV4cGlyYXRpb24gZGF0ZSBvbiBmaWxlIGZvciB0aGUgbmV3IHNldCAoZS5nLiB0aGVyZSBpcyBubyBlbnRyeSBmb3IgYSByZXRhaWxlciBvbiBmaWxlIGluIHRoZSBuZXdlc3QgZGF0YSBzZXQpLCBhc3N1bWUgdGhlIGFjY291bnQgaXMgZXhwaXJlZCAtIG1hcmsgaXQgIkVYUElSRUQiIG9yIGVsc2UgIkFDVElWRSINCg0KMy4gSWYgaXQncyBleHBpcmVkIChlLmcuIHRoZXJlIGlzIG5vIGNvcnJlc3BvbmRpbmcgeSBlbnRyeSBmb3IgYSBnaXZlbiB4IGVudHJ5KSwgbWFyayBhbiBleHBpcmF0aW9uIGRhdGUgYXMgYmVpbmcgdGhlIGxhc3QgYWN0aXZlIGRhdGUgb2YgdGhlIGxpY2Vuc2Ugb24gZmlsZSBmcm9tIHRoZSBleGlzdGluZyBkYXRhYmFzZS4NCg0KNC4gQ3JlYXRlIGEgbmV3IHNldCBvZiBjYW5vbmljYWwgY29sdW1ucyBieSBjaGVja2luZyB0byBzZWUgaWYgYm90aCB4IGFuZCB5IGNvbHVtbnMgYXJlIGZpbGxlZCBmb3IgZWFjaCBvZiBvdXIgdmFyaWFibGVzIC0gaWYgdGhlcmUncyBubyB4LCBwdXQgdGhlIHkgZGF0YSBpbiB0aGVyZSBhbmQgdmljZSB2ZXJzYS4NCg0KNS4gVXNlIHNpbWlsYXIgbG9naWMgdG8gZmlsbCBpbiBhZGRyZXNzLCBjb3VudHksIHB1Ymxpc2ggZGF0ZSBhbmQgbGljZW5zZSB0eXBlIGRhdGEuDQoNCjYuIFJlbW92ZSB0aGUgY29sdW1ucyB3aGljaCBhcmUgbGVmdCBvdmVyIGZyb20gdGhlIGpvaW4gKGUuZy4gYGxhdC54YCwgYGxpY2Vuc2VfdHlwZS55YCkgdXNpbmcgYGRwbHlyOjpzZWxlY3RgDQoNCkluIHRoaXMgam9pbiAqdGhlIG1vc3QgcmVjZW50IGRhdGEgc2V0IGdvZXMgc2Vjb25kIChlLmcuIHRoZSAieSIpIGRhdGEgc2V0LioNCg0KYGBge3Igam9pbmVkX3BhLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9IEZBTFNFLGNhY2hlPVRSVUUsIGV2YWwgPSBGQUxTRX0NCmpvaW5lZF9wYSA8LSBmdWxsX2pvaW4oc3RvcmVkX3BhLCANCiAgICAgICAgICAgICAgICAgICAgIHJldGFpbGVyc19Tb2NyYXRhX3BhLCBieSA9IGMoImFjY291bnQiLCAidHJhZGVfbmFtZSIpKSAlPiUNCiAgbXV0YXRlKGV4cGlyZWRfeV9uID0gaWZlbHNlKGlzLm5hKGV4cGlyYXRpb25fZGF0ZS55KSA9PSBUUlVFLCAiRVhQSVJFRCIsICJBQ1RJVkUiKSwNCiAgICAgICAgIGV4cGlyYXRpb25fZGF0ZSA9IGlmX2Vsc2UoZXhwaXJlZF95X24gPT0gJ0VYUElSRUQnLCBleHBpcmF0aW9uX2RhdGUueCwgeW1kKGV4cGlyYXRpb25fZGF0ZS55KSksDQogICAgICAgICBsYXQgPSBpZmVsc2UoaXMubmEobGF0LngpID09IEZBTFNFLCBsYXQueCwgbGF0LnkpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGlzLm5hKGxvbi54KSA9PSBGQUxTRSwgbG9uLngsIGxvbi55KSwNCiAgICAgICAgIGFkZHJlc3NfZnVsbCA9IGlmZWxzZShpcy5uYShhZGRyZXNzX2Z1bGwueCkgPT0gRkFMU0UsIGFkZHJlc3NfZnVsbC54LCBhZGRyZXNzX2Z1bGwueSksDQogICAgICAgICBjb3VudHkgPSBpZmVsc2UoaXMubmEoY291bnR5LnkpID09IFRSVUUsIGNvdW50eS54LCBjb3VudHkueSksDQogICAgICAgICBwdWJsaXNoX2RhdGUgPSBpZl9lbHNlKGlzLm5hKHB1Ymxpc2hfZGF0ZS55KSA9PSBUUlVFLCBwdWJsaXNoX2RhdGUueCwgcHVibGlzaF9kYXRlLnkpLA0KICAgICAgICAgbGljZW5zZV90eXBlID0gaWZlbHNlKGlzLm5hKGxpY2Vuc2VfdHlwZS55KSA9PSBUUlVFLCBsaWNlbnNlX3R5cGUueCwgbGljZW5zZV90eXBlLnkpLA0KICAgICAgICAgc3RhdGUgPSAiUEEiKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtbGF0LngsIC1sb24ueCwgLWxhdC55LCAtbG9uLnksIA0KICAgICAgICAgLWFkZHJlc3NfZnVsbC54LCAtYWRkcmVzc19mdWxsLnksIC1jb3VudHkueCwgLWNvdW50eS55LA0KICAgICAgICAgLXB1Ymxpc2hfZGF0ZS54LCAtcHVibGlzaF9kYXRlLnksIC1zdGF0ZS54LCAtc3RhdGUueSwgLWxpY2Vuc2VfdHlwZS54LCAtbGljZW5zZV90eXBlLnksDQogICAgICAgICAtZXhwaXJhdGlvbl9kYXRlLngsIC1leHBpcmF0aW9uX2RhdGUueSkNCmBgYA0KDQpVc2luZyB0aGUgYHN1bW1hcnlgIGZ1bmN0aW9uLCB3ZSBjYW4gc2VlIGhvdyBtdWNoIE5BIGRhdGEgd2UgaGF2ZSBmb3Igb3VyIGdlb2xvY2F0aW9ucy4NCg0KYGBge3IgcmV0YWlsZXJfbmFfc3VtbWFyeV9qb2luX3BhLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9IEZBTFNFLGNhY2hlPVRSVUUsIGV2YWwgPSBGQUxTRX0NCnN1bW1hcnkoaXMubmEoam9pbmVkX3BhJGxhdCkpDQpgYGANCg0KIyMgNC40LiBHZW9jb2RlIG1pc3NpbmcgaW5mb3JtYXRpb24NCg0KVGhpcyByb3V0aW5lIGlzIHRoZSBzYW1lIGFzIHNlY3Rpb24gKjMuNCosIGJ1dCBpcyByZS1jYXBpdHVsYXRlZCBoZXJlIHdpdGggdGhlIG9iamVjdCBuYW1lcyByZWxldmFudCB0byB0aGlzIHNlY3Rpb24uIA0KDQpUaGUgZ29vZ2xlIGdlb2NvZGVyIHRha2VzIGEgbGlzdCBvZiBhZGRyZXNzZXMgYW5kIHJldHVybnMgYSBsaXN0IG9mIGxhdC9sb24gcGFpcnMuIA0KDQoxLiBXZSBjcmVhdGUgYSBkYXRhZnJhbWUgb2Ygb2JzZXJ2YXRpb25zIHRvIGJlIGdlb2NvZGVkIChgdG9fZ2VvY29kZV9wYWApIHdoaWNoIGxhY2sgbGF0L2xvbiBkYXRhLiANCg0KMi4gV2Ugc2VuZCB0aGF0IGRhdGEgdG8gdGhlIGdlb2NvZGVyIHZpYSBgZ2dtYXBgIChgZ2VvY29kZWApLg0KDQozLiBXZSBgY2JpbmRgIHRoZSByZXR1cm5lZCBsYXQvbG9uIHBhaXJzIHRvIHRoZSByZXN0IG9mIHRoZSBjb2x1bW5zIGluIHRoZSBgdG9fZ2VvY29kZV9wYWAgZGF0YWZyYW1lDQoNCjQuIFdlIGByYmluZGAgdGhlIHJvd3MgZnJvbSBvdXIgYHRvX2dlb2NvZGVfcGFgIHNldCwgd2hpY2ggbm93IGhhdmUgeC15IGluZm8sIHRvIG91ciBkYXRhIHdoaWNoIGRpZCBub3QgbmVlZCB0byBiZSBnZW9jb2RlZCBpbiB0aGUgZmlyc3QgcGxhY2UgYW5kIGNyZWF0ZSBhIGRhdGFmcmFtZSBgZ2VvY29kZWRfcGFgLg0KDQo1LiBQZXJmb3JtIGEgZGF0YSBjaGVjayB0byBzZWUgd2hhdCBtaWdodCBzdGlsbCBiZSBtaXNzaW5nDQoNCg0KYGBge3IgcmV0YWlsZXJfbmFfc3VtbWFyeV9qb2luXzIsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT0gRkFMU0UsY2FjaGU9VFJVRSwgZXZhbCA9IEZBTFNFfQ0KdG9fZ2VvY29kZV9wYSA8LSBqb2luZWRfcGEgJT4lDQogIGZpbHRlcihpcy5uYShsYXQpID09IFRSVUUpDQoNCmdlb2NvZGVkX3BhIDwtIGdlb2NvZGUodG9fZ2VvY29kZV9wYSRhZGRyZXNzX2Z1bGwsIHNvdXJjZSA9ICAiZ29vZ2xlIikgJT4lDQogIGNiaW5kKC4sIHRvX2dlb2NvZGVfcGEgJT4lDQogICAgICAgICAgc2VsZWN0KC1sYXQsIC1sb24pKSAlPiUNCiAgcmJpbmQoam9pbmVkX3BhICU+JQ0KICAgICAgICAgIGZpbHRlcihpcy5uYShsYXQpID09IEZBTFNFKSwgLikNCg0Kc3VtbWFyeShpcy5uYShnZW9jb2RlZF9wYSRsYXQpKQ0KYGBgDQoNCiMjIDQuNS4gUGVyZm9ybSBxdWFsaXR5IGNoZWNrIGZvciBiYWQgZ2VvY29kZXMNCg0KV2hlbiBnZW9jb2RlcyBhcmUgd3JvbmcsIHRoZXkgYXJlIG9mdGVuIHZlcnkgd3JvbmcsIGFuZCBzZXQgc29tZXdoZXJlIHdpbGRseSBkaWZmZXJlbnQgZnJvbSBvdXIgdGFyZ2V0IGFyZWEuIFdlIGRvIGEgc3BhdGlhbCBqb2luIHdpdGggdGhlIFBBIHNoYXBlZmlsZSwga2VlcCBvbmx5IHRoZSBvYnNlcnZhdGlvbnMgd2hpY2ggZmFsbCBvdXRzaWRlIG9mIFBBLiBXZSBjYW4gY2hlY2sgdGhlc2Ugb2JzZXJ2YXRpb25zIGFuZCBwb3RlbnRpYWxseSBnZW9jb2RlIHRoZW0gbWFudWFsbHkuDQoNCldlIGRvIHRoaXMgdGhyb3VnaCB0aGUgZm9sbG93aW5nIHN0ZXBzOg0KDQoxLiBQZXJmb3JtIGEgc3BhdGlhbCBqb2luIHVzaW5nIGBzdF9qb2luYCBmcm9tIHRoZSBgc2ZgIHBhY2thZ2UgdG8gc2VlIHdoaWNoIHBvaW50cyBmcm9tIGBwYV9nZW9jb2RlZGAgZmFsbCBvdXRzaWRlIFBBIChleGNsdWRpbmcgdGhvc2Ugd2Uga25vdyBhcmUgTkEgYXQgcHJlc2VudCkuIFRvIGRvIHRoaXMgd2UgbmVlZCB0byByZXByb2plY3Qgb3VyIGxhdC9sb24gZGF0YSBhcyBhbiBgc2ZgIHZlY3RvciBvYmplY3QgcHJvamVjdGVkIGluIFdHUzg0ICh3ZWIgbWVyY2F0b3IsIGBjcnMgPSA0MzI2YCkgYW5kIGpvaW4gaXQgdG8gYHBhX3NocGAuDQoNCjIuIFdlIGBmaWx0ZXJgIHRob3NlIG9ic2VydmF0aW9ucyB3aGljaCBjb21lIGJhY2sgYXMgYGlzLm5hKFNUVVNQUykgPT0gVFJVRWAgKGUuZy4gdGhleSBkb24ndCBqb2luIGF0IGFsbCB0byB0aGUgUEEgdGlncmlzIHNoYXBlZmlsZSBhbmQgZG9uJ3QgdGFrZSBvbiBhbnkgb2YgaXQncyBhdHRyaWJ1dGVzIGxpa2UgU1RVU1BTKSBhbmQga25vdyB0aGF0IHRoZXNlIGFyZSB0aGUgb25lcyB3aGljaCBmZWxsIG91dHNpZGUgb3VyIHNoYXBlZmlsZS4NCg0KMy4gV2UgYGRwbHlyOjpzZWxlY3RgIG9ubHkgb3VyIGNhbm9uaWNhbCB2YXJpYWJsZXMgZnJvbSB0aGVzZSBhbmQgZXh0cmFjdCB0aGUgbGF0L2xvbiBmcm9tIHRoZWlyIGdlb21ldHJpZXMgdXNpbmcgYSBgbXV0YXRlYCBzdGF0ZW1lbnQuIFdlIHdhbnQgdG8gdHVybiB0aGlzIGJhY2sgaW50byBhIGRhdGFmcmFtZSB3aXRoIGxhdC9sb24gZGF0YSBhbmQgbm90IGdlb21ldHJpZXMgbGlrZSBhbiBgc2ZgIG9iamVjdC4gU28gd2Ugc2V0IGl0IGBhcy5kYXRhLmZyYW1lYCBhbmQgcmVtb3ZlIHRoZSBgZ2VvbWV0cnlgIGNvbHVtbi4NCg0KNC4gV2Ugbm93IGByYmluZGAgdGhlc2UgZGF0YSBiYWNrIHRvIG91ciBgZ2VvY29kZWRfcGFgIG9ic2VydmF0aW9ucyB3aGljaCBsYWNrZWQgbGF0L2xvbiBpbiB0aGUgZmlyc3QgcGxhY2UgKGBmaWx0ZXIoaXMubmEobGF0KSA9PSBGQUxTRSlgKSwgdG8gcmVjcmVhdGUgb3VyIG9yaWdpbmFsIGBnZW9jb2RlZF9wYWAgZGF0YSBzZXQgd2l0aCBhIGJldHRlciBrbm93bGVkZ2Ugb2Ygd2hhdCBsYWNrcyBnb29kIGxhdC9sb24gZGF0YS4gVGhvc2UgbWlzc2luZyBvciBiYWQgZ2VvY29kZXMgYXJlIG5vdyBhbGwgbGFiZWxlZCBOQS4NCg0KYGBge3IgZGV0ZWN0X2Vycm9yc19wYSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0KZXJyb3JzX3BhIDwtIHN0X2pvaW4oZ2VvY29kZWRfcGEgJT4lIA0KICAgICAgICAgIGZpbHRlcihpcy5uYShsYXQpID09IEZBTFNFKSAlPiUgDQogICAgICAgICAgc3RfYXNfc2YoLiwgY29vcmRzID0gYygibG9uIiwgImxhdCIpLCBjcnMgPSA0MzI2KSwgDQogICAgICAgIHBhX3NocCwgDQogICAgICAgIGpvaW4gPSBzdF93aXRoaW4sIA0KICAgICAgICBsZWZ0ID0gVFJVRSkgJT4lDQogIGZpbHRlcihpcy5uYShTVFVTUFMpID09IFRSVUUpICU+JQ0KICBtdXRhdGUobG9uPW1hcF9kYmwoZ2VvbWV0cnksIH5zdF9jZW50cm9pZCgueClbWzFdXSksDQogICAgICAgICBsYXQ9bWFwX2RibChnZW9tZXRyeSwgfnN0X2NlbnRyb2lkKC54KVtbMl1dKSklPiUNCiAgZHBseXI6OnNlbGVjdChjYW5vbmljYWxfbmFtZXMpICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLWdlb21ldHJ5KSAlPiUNCiAgcmJpbmQoLiwgZ2VvY29kZWRfcGEgJT4lDQogICAgICAgICAgZmlsdGVyKGlzLm5hKGxhdCkgPT0gVFJVRSkpDQpgYGANCg0KIyMgNC42LiBNYW51YWxseSBnZW9jb2RlIHJlbWFpbmluZyBvYnNlcnZhdGlvbnMNCg0KVGhlIG9ic2VydmF0aW9ucyB0aGF0IGRvbid0IGdlb2NvZGUgd2lsbCBuZWVkIHRvIGJlIGRlYWx0IHdpdGggbWFudWFsbHkgKGUuZy4gaW5wdXQgbGF0L2xvbiBjb29yZGluYXRlcyBiYXNlZCBvbiBnb29nbGUgbWFwIHJlc2VhcmNoLCBsaWNlbnNlIHJlYXNlYXJjaCBldGMuLCkuIFNpbWlsYXJseSwgb2JzZXJ2YXRpb25zIHRoYXQgZ2VvY29kZSBwb29ybHkgYW5kIGVuZCB1cCBvbiBvdXIgYGVycm9yc19wYWAgbGlzdCBuZWVkIHRvIGJlIG1hbnVhbGx5IGdlb2NvZGVkLg0KDQpUaGlzIGhhcyB0byBiZSBkb25lIGJ5IGhhbmQgdW5mb3J0dWF0ZWx5LiANCg0KV2Ugd3JpdGUgb3V0IGBlcnJvcnNfcGFgIGFuZCB0aGVuIHR5cGUgaW4gdGhlIGxhdC9sb24gbWFudWFsbHkgaW4gRXhjZWwuIFRoaXMgaXMgbXVjaCBlYXNpZXIgdGhhbiBkb2luZyBhIHNlcmllcyBvZiBgbXV0YXRlYCBzdGF0ZW1lbnRzLg0KDQpUaGUgbmFtZSBvZiB0aGlzIGZpbGVwYXRoIHNob3VsZCBjaGFuZ2UgZWFjaCB0aW1lIHlvdSBkbyBpdCBzbyBhcyBub3QgdG8gb3ZlcndyaXRlIHByZXZpb3VzIHdvcmsuIEFzIHlvdSBhcmUgZ2VvY29kaW5nIHRoZSBvYnNlcnZhdGlvbnMgYnkgaGFuZCwga2VlcCB0aGUgZmlsZSBpbiBjc3YgZm9ybSwgcmF0aGVyIHRoYW4geGxzeC4NCg0KYGBge3Igd3JpdGVfb3V0X3BhX2Vycm9ycywgZXZhbCA9IEZBTFNFLCBjYWNoZSA9IFRSVUV9DQp3cml0ZS5jc3YoZXJyb3JzX3BhLCAiZmlsZXBhdGggZ29lcyBoZXJlLmNzdiIpDQpgYGANCg0KT25jZSB3ZSd2ZSBkb25lIG91ciBnZW9jb2RpbmcgcmVzZWFyY2gsIGFuZCBhZGQgaW4gdGhlIGxhdC9sb24gZGF0YSB3aGljaCB3YXMgbWlzc2luZywgd2UgbG9hZCB0aGUgKG5vdyBjb3JyZWN0KSBkYXRhIGJhY2sgaW4gdXNpbmcgYSBgcmVhZC5jc3ZgIGNvbW1hbmQgYW5kIGpvaW4gaXQgdG8gb3VyIHByb3Blcmx5IGdlb2NvZGVkIGRhdGEuICoqUmVtZW1iZXIgdGhhdCB5b3UgbmVlZCB0byBjaGFuZ2UgdGhlIGZpbGUgcGF0aCBmcm9tIHRoZSBvbmUgaW4gdGhlIGNvZGUgY2h1bmsgYmVsb3cuKioNCg0KV2UgdXNlIGFuIGBhbnRpX2pvaW5gIHRvIGdlb2NvZGVkIHRvIGlzbG9hdGUgdGhvc2Ugb2JzZXJ2YXRpb25zIGZyb20gYGdlb2NvZGVkX3BhYCB0aGF0IGRpZG4ndCBlbmQgdXAgb3VyIGBlcnJvcnNfcGFgIGRhdGEgc2V0LCBhbmQgdGhlbiBiaW5kIHRoZSByb3dzIHRvZ2V0aGVyIHdpdGggdGhlIG1hbnVhbGx5IGNvcnJlY3RlZCBlcnJvcnMuIE5vdGUgdGhhdCBpbnNpZGUgdGhlIGByYmluZGAgZnVuY3Rpb24gd2UgZG8gc29tZSBkYXRhIGNsZWFuaW5nIHdpdGggb3VyIHVwbG9hZGVkIGVycm9ycyBzbyB0aGF0IHdlIGtlZXAgb25seSB0aGUgY2Fub25pY2FsIG5hbWVzIGFuZCBvdXIgY29sdW1uIGRhdGF0eXBlcyBhcmUgaW4gdGhlIGNvcnJlY3QgZm9ybWF0cy4NCg0KTm90ZSB0aGF0IGluc2lkZSB0aGUgYHJiaW5kYCBmdW5jdGlvbiB3ZSBkbyBzb21lIGRhdGEgY2xlYW5pbmcgd2l0aCBvdXIgdXBsb2FkZWQgZXJyb3JzIHNvIHRoYXQgd2Uga2VlcCBvbmx5IHRoZSBjYW5vbmljYWwgbmFtZXMgYW5kIG91ciBjb2x1bW4gZGF0YXR5cGVzIGFyZSBpbiB0aGUgY29ycmVjdCBmb3JtYXRzLiBUaGVyZSBpcyBhbiBpbXBvcnRhbnQgaGl0Y2ggaGVyZSAtIHdoZW4gdGhlIGRhdGEgd2VyZSBleHBvcnRlZCB0byBFeGNlbCBmb3IgbWFudWFsIGNsZWFuaW5nLCB0aGV5IG1heSBoYXZlIGhhZCB0aGVpciBkYXRlcyBzY3JhbWJsZWQgdG8gYSBtb250aC9kYXkveWVhciBmb3JtYXQgcmF0aGVyIHRoYW4gdGhlIHllYXIvbW9udGgvZGF5IG91ciBkYXRhIHVzZS4gSWYgYW4gZXJyb3Igc2hvd3MgdXAgc2F5aW5nICJBbGwgZm9ybWF0cyBmYWlsZWQgdG8gcGFyc2UuIE5vIGZvcm1hdHMgZm91bmQuIiB0aGlzIG1lYW5zIHRoYXQgb25lIG11c3QgYWRqdXN0IHRoZSBgeW1kYCBmdW5jdGlvbiAod2hpY2ggdHVybnMgYSBzdHJpbmcgaW50byBhIGRhdGUgZm9ybWF0KSB0byByZWZsZWN0IHRoZSBjb3JyZWN0IG9yZGVyIChlLmcuIGBtZHlgIGZvciBtb250aC9kYXkveWVhcikuDQoNCmBgYHtyIGZpeF9taXNzaW5nX2luZm8sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT0gRkFMU0UsY2FjaGU9VFJVRSwgZXZhbCA9IEZBTFNFfQ0KZ2VvY29kZWRfcGFfZml4ZWQgPC0gIGdlb2NvZGVkX3BhICU+JSANCiAgYW50aV9qb2luKC4sIGVycm9yc19wYSkgJT4lDQogIHJiaW5kKC4sIHJlYWQuY3N2KCJpbnB1dCBmaWxlcGF0aC5jc3YiKSAlPiUNCiAgZHBseXI6OnNlbGVjdChjYW5vbmljYWxfbmFtZXMpICU+JQ0KICBtdXRhdGVfaWYoaXMuZmFjdG9yLCBhcy5jaGFyYWN0ZXIpICU+JQ0KICBtdXRhdGUoZXhwaXJhdGlvbl9kYXRlID0gbWR5KGV4cGlyYXRpb25fZGF0ZSksDQogICAgICAgICBwdWJsaXNoX2RhdGUgPSBtZHkocHVibGlzaF9kYXRlKSkpDQoNCmBgYA0KDQojIyA0LjcuIFdyaXRlIG91dCB0aGUgZGF0YXNldA0KDQpUaGlzIGlzIG91ciBmaW5hbCBQQSBkYXRhIHNldCAtIGBnZW9jb2RlZF9wYV9maXhlZGAuIEl0IGNhbiBiZSB3cml0dGVuIG91dCB0byBmaWxlLiBXZSB3aWxsIGJpbmQgaXQgdG8gb3VyIERFIGFuZCBOSiBkYXRhIGxhdGVyIGluIHRoZSBjb2RlIGJhc2UuDQoNCmBgYHtyIGV4cG9ydF9wYSwgd2FybmluZz0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBUUlVFLCBldmFsID0gRkFMU0V9DQp3cml0ZS5jc3YoZ2VvY29kZWRfcGFfZml4ZWQsICJmaWxlcGF0aCBnb2VzIGhlcmUuY3N2IikNCmBgYA0KDQoNCiMgNS4gUHJlY3Vyc29yIFJvdXRpbmUgLSBCYXNlbGluZSBOSiBEYXRhDQoNClRoZSBOZXcgSmVyc2V5IGRhdGEgcm91dGluZSBpcyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBmcm9tIHRoZSBQQSBhbmQgREUgcm91dGluZXMgd2hpY2ggdXNlIFNPREEgQVBJIGNhbGxzIGFuZCByZWxhdGl2ZWx5IHN0YW5kYXJkaXplZCBkYXRhLiBXZSBoYXZlIE5KIGRhdGEgZnJvbSBNYXJjaCBvZiAyMDE4IGFuZCBBdWd1c3Qgb2YgMjAxOS4gVGhlIGxhdHRlciBkYXRhIHNldCBpcyB0aG91Z2h0IHRvIGJlIHJlbGF0aXZlbHkgY29tcGxldGUuIEhvd2V2ZXIsIE5ldyBKZXJzZXkgaGFzIG5vdCBnZW9jb2RlZCBpdHMgZGF0YSwgc28gdGhpcyBiYXNlbGluZSByb3V0aW5lIGJvdGggY29tcGFyZXMgdGhlIDIwMTggYW5kIDIwMTkgZGF0YSBhbmQgaW50ZWdyYXRlcyB0aGVtLCBidXQgaXQgZ2VvY29kZXMgb3VyIGJhc2VsaW5lIGRhdGEgc2V0Lg0KDQojIyA1LjEuIFJlYWQgTWFyY2gsIDIwMTggRGF0YQ0KDQoxLiBXZSByZWFkIHRoZSAyMDE4IGluZm8gZnJvbSBleGNlbCAoYHJlYWRfZXhjZWxgKS4NCg0KMi4gV2UgcHVsbCBhIGZpdmUgZGlnaXQgemlwIGNvZGUgb3V0IGZyb20gYSBuaW5lIGRpZ2l0IGNvZGUgdXNlZCBpbiB0aGVpciBkYXRhIHNldC4gKHNlZTogYFppcGApLg0KDQozLiBXZSBjcmVhdGUgYW4gYGFkZHJlc3NfZnVsbGAgZmllbGQgZm9yIGdlb2NvZGluZyBhbmQgcmVwbGFjZSAiJiIgY2hhcmFjdGVycyB3aXRoICJBTkQiLg0KDQo0LiBXZSBhZGQgY29sdW1ucyBvciBjaGFuZ2UgbmFtZXMgdG8gY29ycmVzcG9uZCB0byBvdXIgImNhbm9uaWNhbCIgY29sdW1uIG5hbWVzLg0KDQpgYGB7ciBsb2FkX05KLCBjYWNoZSA9IFRSVUUsIGV2YWwgPSBGQUxTRX0NCk5KXzNfMjAxOCA8LSByZWFkX2V4Y2VsKCIvL2pvdmUuZGVzaWduLnVwZW5uLmVkdS9EZXB0LVNoYXJlcy9wcmF4LzAxIFByb2plY3QgRm9sZGVycy8yMDE5X0FubmVuYmVyZ19HZW9TY2FubmluZy9pbnB1dERhdGEvUmV0YWlsZXJNYXBzL1JldGFpbGVyTWFwcy9OSiAobmNvb3BlckBhc2MudXBlbm4uZWR1KS9DYXJyZXJhcyBUYXJ0YWsgSiBXMTMwMjk0IFByb3ZpZGVkIFcxMzAyOTQgLSBDaWdhcmV0dGUgcmV0YWlsZXJzLnhsc3giLCBza2lwID0gNSkgJT4lDQogIG11dGF0ZShaaXAgPSBpZmVsc2Uoc3RyX2xlbmd0aChaaXApID09IDksIHN0cl9zdWIoWmlwLCBlbmQgPSAtNSksIFppcCkpJT4lDQogIHVuaXRlKC4sICJhZGRyZXNzX2Z1bGwiLCBzZXAgPSAiLCAiLA0KICAgICAgICBjKCJTdHJlZXQiLCAiQ2l0eSIsICJTdGF0ZSIsICJaaXAiKSkgJT4lDQogIG11dGF0ZShhZGRyZXNzX2Z1bGwgPSBzdHJfcmVwbGFjZShhZGRyZXNzX2Z1bGwsICImIiwgIkFORCIpKSU+JQ0KICBtdXRhdGUoZXhwaXJlZF95X24gPSAiQUNUSVZFIiwgDQogICAgICAgICBleHBpcmF0aW9uX2RhdGUgPSB5bWQoIjIwMTktMDMtMzEiKSwgICMgaXMgdGhpcyBjb3JyZWN0Pw0KICAgICAgICAgcHVibGlzaF9kYXRlID0geW1kKCIyMDE4LTA0LTAxIiksDQogICAgICAgICBhY2NvdW50ID0gc3RyX2MoIk5KXzE4Iiwgcm93X251bWJlcigpLCBzZXAgPSAiXyIpLCANCiAgICAgICAgIGxpY2Vuc2VfdHlwZSA9IE5BLCBjb3VudHkgPSBOQSwNCiAgICAgICAgIHN0YXRlID0gIk5KIiwNCiAgICAgICAgIFRyYWRlID0gaWZlbHNlKGlzLm5hKFRyYWRlKSA9PSBUUlVFLCBOYW1lLCBUcmFkZSkpICU+JSANCiAgICByZW5hbWUodHJhZGVfbmFtZSA9IFRyYWRlKSAlPiUgDQogICAgZHBseXI6OnNlbGVjdCgtTG9jX0NvZGUsIC1OYW1lKQ0KDQoNCmBgYA0KDQoNCg0KIyMgNS4yLiBHZW9jb2RlIERhdGENCg0KV2UgZG8gYSBwcmVsaW1pbmFyeSBnZW9jb2RlIC0gcmVjYWxsLCBub25lIG9mIHRoZSBhZGRyZXNzZXMgaGF2ZSBsYXQvbG9uIGluZm9ybWF0aW9uLCBzbyB0aGlzIHByb2NlZHVyZSBpcyBkaWZmZXJlbnQgdGhhbiB0aGUgcHJlY2VkaW5nIFBBIHNlY3Rpb25zLiBXZSBhcmUgZ2VvY29kaW5nIGFsbCBvZiB0aGUgZGF0YSwgYW5kIHdlIGV4cGVjdCB0byBoYXZlIGxvdHMgb2YgZXJyb3JzIGFuZCBhbWJpZ3VpdGllcyB0byBzb3J0IG91dC4NCg0KMS4gV2Ugc2VuZCBhIHZlY3RvciBvZiBhZGRyZXNzZXMgdG8gdGhlIGdlb2NvZGVyIHZpYSBgZ2dtYXBgIChgZ2VvY29kZWApLg0KDQoyLiBXZSBgY2JpbmRgIHRoZSByZXR1cm5lZCBsYXQvbG9uIHBhaXJzIHRvIHRoZSByZXN0IG9mIHRoZSBjb2x1bW5zIGluIHRoZSBgTkpfM18yMDE4YCBkYXRhZnJhbWUsIGNyZWF0aW5nIGEgbmV3IGRhdGFmcmFtZSBgTkpfM18yMDE4X2dlb2NvZGVkYA0KDQozLiBXZSB0aGVuIGNoZWNrIHRoZSBkYXRhIHRvIHNlZSBob3cgbWFueSBvYnNlcnZhdGlvbnMgZmFpbGVkIChgc3VtbWFyeWApDQoNCmBgYHtyIGdlb2NvZGVfb2xkX05KLCByZXN1bHRzID0gJ2hpZGUnLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9IEZBTFNFLGNhY2hlPVRSVUUsIGV2YWwgPSBGQUxTRX0NCg0KTkpfM18yMDE4X2dlb2NvZGVkIDwtIGdlb2NvZGUoTkpfM18yMDE4JGFkZHJlc3NfZnVsbCwgc291cmNlID0gICJnb29nbGUiKSAlPiUNCiAgY2JpbmQoLiwgTkpfM18yMDE4KQ0KDQpzdW1tYXJ5KGlzLm5hKE5KXzNfMjAxOF9nZW9jb2RlZCRsYXQpKQ0KDQpgYGANCg0KIyMgNS4zLiBMb2FkIEF1Z3VzdCwgMjAxOSBEYXRhDQoNCkxvYWQgdGhlIHNlY29uZCBkYXRhIHNldCB1c2luZyB0aGUgc2FtZSByb3V0aW5lIGFzIGluIHNlY3Rpb24gKjUuMSoNCg0KVGhlcmUgYXJlIGFsbW9zdCB0d2ljZSBhcyBtYW55IG9ic2VydmF0aW9ucyBhcyBvdXIgMjAxOCBkYXRhLiBXZSBoYXZlIGNvbWUgdG8gbGVhcm4gdGhhdCB0aGlzIGlzIGJlY2F1c2UgMjAxOCBpcyBhbiBpbmNvbXBsZXRlIGRhdGEgc2V0Lg0KDQpgYGB7ciBsb2FkX05KXzIsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0KTkpfOF8yMDE5IDwtIHJlYWRfZXhjZWwoIi8vam92ZS5kZXNpZ24udXBlbm4uZWR1L0RlcHQtU2hhcmVzL3ByYXgvMDEgUHJvamVjdCBGb2xkZXJzLzIwMTlfQW5uZW5iZXJnX0dlb1NjYW5uaW5nL2lucHV0RGF0YS9SZXRhaWxlck1hcHMvUmV0YWlsZXJNYXBzL05KIChuY29vcGVyQGFzYy51cGVubi5lZHUpL0NhcnJlcmFzLVRhcnRhayBKIEMxNDg3NjQgQ2lnYXJldHRlIHJldGFpbGVycy54bHN4Iiwgc2tpcCA9IDUpICAlPiUNCiAgbXV0YXRlKFppcCA9IGlmZWxzZShzdHJfbGVuZ3RoKFppcCkgPT0gOSwgc3RyX3N1YihaaXAsIGVuZCA9IC01KSwgWmlwKSklPiUNCiAgdW5pdGUoLiwgImFkZHJlc3NfZnVsbCIsIHNlcCA9ICIsICIsDQogICAgICAgIGMoIlN0cmVldCIsICJDaXR5IiwgIlN0YXRlIiwgIlppcCIpKSAlPiUNCiAgbXV0YXRlKGFkZHJlc3NfZnVsbCA9IHN0cl9yZXBsYWNlKGFkZHJlc3NfZnVsbCwgIiYiLCAiQU5EIikpJT4lDQogIG11dGF0ZShleHBpcmVkX3lfbiA9ICJBQ1RJVkUiLCANCiAgICAgICAgIGV4cGlyYXRpb25fZGF0ZSA9IHltZCgiMjAyMC0wMy0zMSIpLA0KICAgICAgICAgcHVibGlzaF9kYXRlID0geW1kKCIyMDE5LTA0LTAxIiksDQogICAgICAgICBhY2NvdW50ID0gc3RyX2MoIk5KXzE5Iiwgcm93X251bWJlcigpLCBzZXAgPSAiXyIpLCANCiAgICAgICAgIGxpY2Vuc2VfdHlwZSA9IE5BLCBjb3VudHkgPSBOQSwNCiAgICAgICAgIHN0YXRlID0gIk5KIiwNCiAgICAgICAgIFRyYWRlID0gaWZlbHNlKGlzLm5hKFRyYWRlKSA9PSBUUlVFLCBOYW1lLCBUcmFkZSkpICU+JSANCiAgICByZW5hbWUodHJhZGVfbmFtZSA9IFRyYWRlKSAlPiUgDQogICAgZHBseXI6OnNlbGVjdCgtTG9jX0NvZGUsIC1OYW1lKQ0KYGBgDQoNCiMjIDUuNC4gQ29hbGVzY2UgRGF0YSBTZXRzDQoNCkpvaW4gdGhlIHR3byBkYXRhIHNldHMsIHNlZSB3aGF0J3MgbmV3LiBJZiB0aGVyZSBpcyBhIG5ldyBhbmQgb2xkIHJlY29yZCwgYXNzaWduIElEIGFzIHRoZSBvbGRlc3Qgb2YgdGhlIHR3byBhY2NvdW50IG51bWJlcnMsIGNoYW5nZSB0aGUgZXhwaXJhdGlvbiBkYXRlLg0KDQpJbiB0aGlzIGpvaW4gdGhlIG1vc3QgcmVjZW50IGRhdGEgc2V0IGdvZXMgc2Vjb25kIChlLmcuIHRoZSAieSIpIGRhdGEgc2V0Lg0KDQpgYGB7ciBjb2FsZXNjZV9OSiwgY2FjaGUgPSBUUlVFLCBldmFsID0gRkFMU0V9DQpOSl9qb2luIDwtIGZ1bGxfam9pbihOSl8zXzIwMThfZ2VvY29kZWQsIE5KXzhfMjAxOSwgYnkgPSAiYWRkcmVzc19mdWxsIikgJT4lDQptdXRhdGUoZXhwaXJlZF95X24gPSBpZmVsc2UoaXMubmEoZXhwaXJhdGlvbl9kYXRlLnkpID09IFRSVUUsICJFWFBJUkVEIiwgIkFDVElWRSIpLA0KICAgICAgICAgZXhwaXJhdGlvbl9kYXRlID0gaWZfZWxzZShleHBpcmVkX3lfbiA9PSAnRVhQSVJFRCcsIGV4cGlyYXRpb25fZGF0ZS54LCB5bWQoZXhwaXJhdGlvbl9kYXRlLnkpKSwNCiAgICAgICAgIGNvdW50eSA9IGlmZWxzZShpcy5uYShjb3VudHkueSkgPT0gVFJVRSwgY291bnR5LngsIGNvdW50eS55KSwNCiAgICAgICAgIHB1Ymxpc2hfZGF0ZSA9IGlmX2Vsc2UoaXMubmEocHVibGlzaF9kYXRlLnkpID09IFRSVUUsIHB1Ymxpc2hfZGF0ZS54LCBwdWJsaXNoX2RhdGUueSksDQogICAgICAgICBsaWNlbnNlX3R5cGUgPSBpZmVsc2UoaXMubmEobGljZW5zZV90eXBlLnkpID09IFRSVUUsIGxpY2Vuc2VfdHlwZS54LCBsaWNlbnNlX3R5cGUueSksDQogICAgICAgICBhY2NvdW50ID0gaWZlbHNlKGlzLm5hKGFjY291bnQueCkgPT0gRkFMU0UsIGFjY291bnQueCwgYWNjb3VudC55KSwNCiAgICAgICAgIHRyYWRlX25hbWUgPSBpZmVsc2UoaXMubmEodHJhZGVfbmFtZS55KSA9PSBGQUxTRSwgdHJhZGVfbmFtZS55LCB0cmFkZV9uYW1lLngpLA0KICAgICAgICAgZXhwaXJhdGlvbl9kYXRlID0gaWZfZWxzZShpcy5uYShleHBpcmF0aW9uX2RhdGUueSkgPT0gRkFMU0UsIGV4cGlyYXRpb25fZGF0ZS55LCBleHBpcmF0aW9uX2RhdGUueCksDQogICAgICAgICBleHBpcmF0aW9uX2RhdGUgPSBpZl9lbHNlKGlzLm5hKGV4cGlyYXRpb25fZGF0ZS55KSA9PSBGQUxTRSwgZXhwaXJhdGlvbl9kYXRlLnksIGV4cGlyYXRpb25fZGF0ZS54KSwNCiAgICAgICAgIHB1Ymxpc2hfZGF0ZSA9IGlmX2Vsc2UoaXMubmEocHVibGlzaF9kYXRlLngpID09IEZBTFNFLCBwdWJsaXNoX2RhdGUueCwgcHVibGlzaF9kYXRlLnkpLA0KICAgICAgICAgc3RhdGUgPSAiTkoiLA0KICAgICAgICAgY291bnR5ID0gTkEpICU+JQ0KICBzZWxlY3QoLXN0YXRlLngsIC0gc3RhdGUueSwgLWNvdW50eS55LCAtY291bnR5LngsIC1hY2NvdW50LngsIC1hY2NvdW50LnksIC10cmFkZV9uYW1lLnksIC10cmFkZV9uYW1lLngsDQogICAgICAgICAtZXhwaXJlZF95X24ueCwgLWV4cGlyZWRfeV9uLnksIC1leHBpcmF0aW9uX2RhdGUueSwgLWV4cGlyYXRpb25fZGF0ZS54LCAtbGljZW5zZV90eXBlLngsIC1saWNlbnNlX3R5cGUueSwNCiAgICAgICAgIC1wdWJsaXNoX2RhdGUueCwgLXB1Ymxpc2hfZGF0ZS55KQ0KYGBgDQoNCg0KV2UgY2FuIGNoZWNrIG91ciBkYXRhIHRvIHNlZSBob3cgbWFueSB1bmlxdWUgYWRkcmVzc2VzIHdlIGhhdmUuDQoNCmBgYHtyIHRlc3RfZHVwbGljYXRlcywgY2FjaGUgPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9IEZBTFNFLCBldmFsID0gRkFMU0V9DQpOSl9qb2luICU+JSANCiAgZHBseXI6OnNlbGVjdChhZGRyZXNzX2Z1bGwpICU+JSANCiAgdW5pcXVlKCkgJT4lIA0KICBucm93KCkNCg0KTkpfam9pbiAlPiUNCiAgZHBseXI6OnNlbGVjdChhZGRyZXNzX2Z1bGwsIHRyYWRlX25hbWUpJT4lIA0KICB1bmlxdWUoKSAlPiUgDQogIG5yb3coKQ0KYGBgDQoNCiMjIDUuNS4gR2VvY29kZSBtaXNzaW5nIG9ic2VydmF0aW9ucw0KDQpOb3cgd2UgdW5kZXJ0YWtlIGEgc2Vjb25kIHJvdW5kIG9mIGdlb2NvZGluZyB0byBhZGQgbGF0L2xvbiBpbmZvcm1hdGlvbiB0byBvdXIgZGF0YSBzZXQgLSBvdXIgbmV3ZXN0IG9ic2VydmF0aW9ucyBkb24ndCBoYXZlIGFueSBsYXQvbG9uIGluZm9ybWF0aW9uLg0KDQpUaGlzIHJvdXRpbmUgaXMgdGhlIHNhbWUgYXMgc2VjdGlvbiAqMy40KiwgYnV0IGlzIHJlLWNhcGl0dWxhdGVkIGhlcmUgd2l0aCB0aGUgb2JqZWN0IG5hbWVzIHJlbGV2YW50IHRvIHRoaXMgc2VjdGlvbi4gDQoNClRoZSBnb29nbGUgZ2VvY29kZXIgdGFrZXMgYSBsaXN0IG9mIGFkZHJlc3NlcyBhbmQgcmV0dXJucyBhIGxpc3Qgb2YgbGF0L2xvbiBwYWlycy4gDQoNCjEuIFdlIGNyZWF0ZSBhIGRhdGFmcmFtZSBvZiBvYnNlcnZhdGlvbnMgdG8gYmUgZ2VvY29kZWQgKGB0b19nZW9jb2RlX25qYCkgd2hpY2ggbGFjayBsYXQvbG9uIGRhdGEuIA0KDQoyLiBXZSBzZW5kIHRoYXQgZGF0YSB0byB0aGUgZ2VvY29kZXIgdmlhIGBnZ21hcGAgKGBnZW9jb2RlYCkuDQoNCjMuIFdlIGBjYmluZGAgdGhlIHJldHVybmVkIGxhdC9sb24gcGFpcnMgdG8gdGhlIHJlc3Qgb2YgdGhlIGNvbHVtbnMgaW4gdGhlIGB0b19nZW9jb2RlX25qYCBkYXRhZnJhbWUNCg0KNC4gV2UgYHJiaW5kYCB0aGUgcm93cyBmcm9tIG91ciBgdG9fZ2VvY29kZV9uamAgc2V0LCB3aGljaCBub3cgaGF2ZSB4LXkgaW5mbywgdG8gb3VyIGRhdGEgd2hpY2ggZGlkIG5vdCBuZWVkIHRvIGJlIGdlb2NvZGVkIGluIHRoZSBmaXJzdCBwbGFjZSBhbmQgY3JlYXRlIGEgZGF0YWZyYW1lIGBnZW9jb2RlZF9uamAuDQoNCjUuIFBlcmZvcm0gYSBkYXRhIGNoZWNrIHRvIHNlZSB3aGF0IG1pZ2h0IHN0aWxsIGJlIG1pc3NpbmcgKGBzdW1tYXJ5YCkNCg0KDQpgYGB7ciBual9yZXRhaWxlcl9uYV9zdW1tYXJ5X2pvaW4sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT0gRkFMU0UsY2FjaGU9VFJVRSwgZXZhbCA9IEZBTFNFfQ0KdG9fZ2VvY29kZV9uaiA8LSBOSl9qb2luICU+JQ0KICBmaWx0ZXIoaXMubmEobGF0KSA9PSBUUlVFKQ0KDQpnZW9jb2RlZF9uaiA8LSBnZW9jb2RlKHRvX2dlb2NvZGVfbmokYWRkcmVzc19mdWxsLCBzb3VyY2UgPSAgImdvb2dsZSIpICU+JQ0KICBjYmluZCguLCB0b19nZW9jb2RlX25qICU+JQ0KICAgICAgICAgIHNlbGVjdCgtbGF0LCAtbG9uKSkgJT4lDQogIHJiaW5kKE5KX2pvaW4gJT4lDQogICAgICAgICAgZmlsdGVyKGlzLm5hKGxhdCkgPT0gRkFMU0UpLCAuKQ0KDQpzdW1tYXJ5KGlzLm5hKGdlb2NvZGVkX25qJGxhdCkpDQpgYGANCg0KIyMgNS42LiBQZXJmb3JtIGRhdGEgcXVhbGl0eSBjaGVjayBmb3IgYmFkIGdlb2NvZGVzDQoNCipUaGlzIHNlY3Rpb24gaXMgcm91Z2hseSB0aGUgc2FtZSBhcyAzLjUgLSB0aGUgbWV0aG9kb2xvZ3kgaXMgcmVwcmludGVkIGhlcmUgdXNpbmcgdGhlIGFwcHJvcHJpYXRlIG9iamVjdCBuYW1lcyoNCg0KV2hlbiBnZW9jb2RlcyBhcmUgd3JvbmcsIHRoZXkgYXJlIG9mdGVuIHZlcnkgd3JvbmcsIGFuZCBzZXQgc29tZXdoZXJlIHdpbGRseSBkaWZmZXJlbnQgZnJvbSBvdXIgdGFyZ2V0IGFyZWEuIFdlIGRvIGEgc3BhdGlhbCBqb2luIHdpdGggdGhlIE5KIHNoYXBlZmlsZSwga2VlcCBvbmx5IHRoZSBvYnNlcnZhdGlvbnMgd2hpY2ggZmFsbCBvdXRzaWRlIG9mIE5KLiBXZSBjYW4gY2hlY2sgdGhlc2Ugb2JzZXJ2YXRpb25zIGFuZCBwb3RlbnRpYWxseSBnZW9jb2RlIHRoZW0gbWFudWFsbHkuDQoNCldlIGRvIHRoaXMgdGhyb3VnaCB0aGUgZm9sbG93aW5nIHN0ZXBzOg0KDQoxLiBQZXJmb3JtIGEgc3BhdGlhbCBqb2luIHVzaW5nIGBzdF9qb2luYCBmcm9tIHRoZSBgc2ZgIHBhY2thZ2UgdG8gc2VlIHdoaWNoIHBvaW50cyBmcm9tIGBnZW9jb2RlZF9uamAgZmFsbCBvdXRzaWRlIFBBIChleGNsdWRpbmcgdGhvc2Ugd2Uga25vdyBhcmUgTkEgYXQgcHJlc2VudCkuIFRvIGRvIHRoaXMgd2UgbmVlZCB0byByZXByb2plY3Qgb3VyIGxhdC9sb24gZGF0YSBhcyBhbiBgc2ZgIHZlY3RvciBvYmplY3QgcHJvamVjdGVkIGluIFdHUzg0ICh3ZWIgbWVyY2F0b3IsIGBjcnMgPSA0MzI2YCkgYW5kIGpvaW4gaXQgdG8gYG5qX3NocGAuDQoNCjIuIFdlIGBmaWx0ZXJgIHRob3NlIG9ic2VydmF0aW9ucyB3aGljaCBjb21lIGJhY2sgYXMgYGlzLm5hKFNUVVNQUykgPT0gVFJVRWAgKGUuZy4gdGhleSBkb24ndCBqb2luIGF0IGFsbCB0byB0aGUgTkogdGlncmlzIHNoYXBlZmlsZSBhbmQgZG9uJ3QgdGFrZSBvbiBhbnkgb2YgaXQncyBhdHRyaWJ1dGVzIGxpa2UgU1RVU1BTKSBhbmQga25vdyB0aGF0IHRoZXNlIGFyZSB0aGUgb25lcyB3aGljaCBmZWxsIG91dHNpZGUgb3VyIHNoYXBlZmlsZS4NCg0KMy4gV2UgYGRwbHlyOjpzZWxlY3RgIG9ubHkgb3VyIGNhbm9uaWNhbCB2YXJpYWJsZXMgZnJvbSB0aGVzZSBhbmQgZXh0cmFjdCB0aGUgbGF0L2xvbiBmcm9tIHRoZWlyIGdlb21ldHJpZXMgdXNpbmcgYSBgbXV0YXRlYCBzdGF0ZW1lbnQuIFdlIHdhbnQgdG8gdHVybiB0aGlzIGJhY2sgaW50byBhIGRhdGFmcmFtZSB3aXRoIGxhdC9sb24gZGF0YSBhbmQgbm90IGdlb21ldHJpZXMgbGlrZSBhbiBgc2ZgIG9iamVjdC4gU28gd2Ugc2V0IGl0IGBhcy5kYXRhLmZyYW1lYCBhbmQgcmVtb3ZlIHRoZSBgZ2VvbWV0cnlgIGNvbHVtbi4NCg0KNC4gV2Ugbm93IGByYmluZGAgdGhlc2UgZGF0YSBiYWNrIHRvIG91ciBgZ2VvY29kZWRfbmpgIG9ic2VydmF0aW9ucyB3aGljaCBsYWNrZWQgbGF0L2xvbiBpbiB0aGUgZmlyc3QgcGxhY2UsIHRvIGNyZWF0ZSBhIGRhdGEgc2V0IGNvbnNpc3Rpbmcgb2YgYWxsIG91ciBvYnNlcnZhdGlvbnMgd2hpY2ggd2UgcHJlc3VtZSBoYXZlIGJhZCBnZW9jb2Rlcy4NCg0KYGBge3IgZGV0ZWN0X2Vycm9yc19uaiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0KZXJyb3JzX25qIDwtIHN0X2pvaW4oZ2VvY29kZWRfbmogJT4lIA0KICAgICAgICAgIGZpbHRlcihpcy5uYShsYXQpID09IEZBTFNFKSAlPiUgDQogICAgICAgICAgc3RfYXNfc2YoLiwgY29vcmRzID0gYygibG9uIiwgImxhdCIpLCBjcnMgPSA0MzI2KSwgDQogICAgICAgIG5qX3NocCwgDQogICAgICAgIGpvaW4gPSBzdF93aXRoaW4sIA0KICAgICAgICBsZWZ0ID0gVFJVRSkgJT4lDQogIGZpbHRlcihpcy5uYShTVFVTUFMpID09IFRSVUUpICU+JQ0KICBtdXRhdGUobG9uPW1hcF9kYmwoZ2VvbWV0cnksIH5zdF9jZW50cm9pZCgueClbWzFdXSksDQogICAgICAgICBsYXQ9bWFwX2RibChnZW9tZXRyeSwgfnN0X2NlbnRyb2lkKC54KVtbMl1dKSklPiUNCiAgZHBseXI6OnNlbGVjdChjYW5vbmljYWxfbmFtZXMpICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLWdlb21ldHJ5KSAlPiUNCiAgcmJpbmQoLiwgZ2VvY29kZWRfbmogJT4lDQogICAgICAgICAgZmlsdGVyKGlzLm5hKGxhdCkgPT0gVFJVRSkpDQpgYGANCg0KV2UgcGVyZm9ybSBhIGRhdGEgY2hlY2sgdG8gc2VlIGhvdyBtYW55IE5BIHZhbHVlcyB3ZSBzdGlsbCBoYXZlIGluIG91ciBkYXRhLg0KDQpgYGB7ciBob3dfbWFueV9uYSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0KDQpzdW1tYXJ5KGlzLm5hKGVycm9yc19uaiRsYXQpKQ0KDQpucm93KGVycm9yc19uaikNCg0KYGBgDQoNCiMjIDUuNy4gTWFudWFsbHkgZ2VvY29kZSBtaXNzaW5nIG9ic2VydmF0aW9ucw0KDQoqVGhpcyByb3V0aW5lIGlzIHNpbWlsYXIgdG8gU2VjdGlvbiA0LjYgYnV0IGlzIHJlLXdyaXR0ZW4gaGVyZSB3aXRoIGFwcHJvcHJpYXRlIG9iamVjdCBuYW1lcyoNCg0KVGhlIG9ic2VydmF0aW9ucyB0aGF0IGRvbid0IGdlb2NvZGUgd2lsbCBuZWVkIHRvIGJlIGRlYWx0IHdpdGggbWFudWFsbHkgKGUuZy4gaW5wdXQgbGF0L2xvbiBjb29yZGluYXRlcyBiYXNlZCBvbiBnb29nbGUgbWFwIHJlc2VhcmNoLCBsaWNlbnNlIHJlYXNlYXJjaCBldGMuLCkuIFNpbWlsYXJseSwgb2JzZXJ2YXRpb25zIHRoYXQgZ2VvY29kZSBwb29ybHkgYW5kIGVuZCB1cCBvbiBvdXIgYGVycm9yc19uamAgbGlzdCBuZWVkIHRvIGJlIG1hbnVhbGx5IGdlb2NvZGVkLg0KDQpUaGlzIGhhcyB0byBiZSBkb25lIGJ5IGhhbmQgdW5mb3J0dWF0ZWx5LiANCg0KV2Ugd3JpdGUgb3V0IGBlcnJvcnNfbmpgIGFuZCB0aGVuIHR5cGUgaW4gdGhlIGxhdC9sb24gbWFudWFsbHkgaW4gRXhjZWwuIFRoaXMgaXMgbXVjaCBlYXNpZXIgdGhhbiBkb2luZyBhIHNlcmllcyBvZiBgbXV0YXRlYCBzdGF0ZW1lbnRzLg0KDQpUaGUgbmFtZSBvZiB0aGlzIGZpbGVwYXRoIHNob3VsZCBjaGFuZ2UgZWFjaCB0aW1lIHlvdSBkbyBpdCBzbyBhcyBub3QgdG8gb3ZlcndyaXRlIHByZXZpb3VzIHdvcmsuIEFzIHlvdSBhcmUgZ2VvY29kaW5nIHRoZSBvYnNlcnZhdGlvbnMgYnkgaGFuZCwga2VlcCB0aGUgZmlsZSBpbiBjc3YgZm9ybSwgcmF0aGVyIHRoYW4geGxzeC4NCg0KYGBge3Igd3JpdGVfb3V0X25qX2Vycm9ycywgZXZhbCA9IEZBTFNFLCBjYWNoZSA9IFRSVUUsIGV2YWwgPSBGQUxTRX0NCndyaXRlLmNzdihlcnJvcnNfbmosICIvL2pvdmUuZGVzaWduLnVwZW5uLmVkdS9EZXB0LVNoYXJlcy9wcmF4LzAxIFByb2plY3QgRm9sZGVycy8yMDE5X0FubmVuYmVyZ19HZW9TY2FubmluZy9kYXRhT3V0cHVycy9lcnJvcnNfbmpfNl85XzIwLmNzdiIpDQpgYGANCg0KT25jZSB3ZSd2ZSBkb25lIG91ciBnZW9jb2RpbmcgcmVzZWFyY2gsIGFuZCBhZGQgaW4gdGhlIGxhdC9sb24gZGF0YSB3aGljaCB3YXMgbWlzc2luZywgd2UgbG9hZCB0aGUgKG5vdyBjb3JyZWN0KSBkYXRhIGJhY2sgaW4gdXNpbmcgYSBgcmVhZC5jc3ZgIGNvbW1hbmQgYW5kIGpvaW4gaXQgdG8gb3VyIHByb3Blcmx5IGdlb2NvZGVkIGRhdGEuDQoNCldlIHVzZSBhbiBgYW50aV9qb2luYCB0byBnZW9jb2RlZCB0byBpc2xvYXRlIHRob3NlIG9ic2VydmF0aW9ucyBmcm9tIGBnZW9jb2RlZF9uamAgdGhhdCBkaWRuJ3QgZW5kIHVwIG91ciBgZXJyb3JzX25qYCBkYXRhIHNldCwgYW5kIHRoZW4gYmluZCB0aGUgcm93cyB0b2dldGhlciB3aXRoIHRoZSBtYW51YWxseSBjb3JyZWN0ZWQgZXJyb3JzLiANCg0KTm90ZSB0aGF0IGluc2lkZSB0aGUgYHJiaW5kYCBmdW5jdGlvbiB3ZSBkbyBzb21lIGRhdGEgY2xlYW5pbmcgd2l0aCBvdXIgdXBsb2FkZWQgZXJyb3JzIHNvIHRoYXQgd2Uga2VlcCBvbmx5IHRoZSBjYW5vbmljYWwgbmFtZXMgYW5kIG91ciBjb2x1bW4gZGF0YXR5cGVzIGFyZSBpbiB0aGUgY29ycmVjdCBmb3JtYXRzLiBUaGVyZSBpcyBhbiBpbXBvcnRhbnQgaGl0Y2ggaGVyZSAtIHdoZW4gdGhlIGRhdGEgd2VyZSBleHBvcnRlZCB0byBFeGNlbCBmb3IgbWFudWFsIGNsZWFuaW5nLCB0aGV5IG1heSBoYXZlIGhhZCB0aGVpciBkYXRlcyBzY3JhbWJsZWQgdG8gYSBtb250aC9kYXkveWVhciBmb3JtYXQgcmF0aGVyIHRoYW4gdGhlIHllYXIvbW9udGgvZGF5IG91ciBkYXRhIHVzZS4gSWYgYW4gZXJyb3Igc2hvd3MgdXAgc2F5aW5nICJBbGwgZm9ybWF0cyBmYWlsZWQgdG8gcGFyc2UuIE5vIGZvcm1hdHMgZm91bmQuIiB0aGlzIG1lYW5zIHRoYXQgb25lIG11c3QgYWRqdXN0IHRoZSBgeW1kYCBmdW5jdGlvbiAod2hpY2ggdHVybnMgYSBzdHJpbmcgaW50byBhIGRhdGUgZm9ybWF0KSB0byByZWZsZWN0IHRoZSBjb3JyZWN0IG9yZGVyIChlLmcuIGBtZHlgIGZvciBtb250aC9kYXkveWVhcikuDQoNCldlIGFsc28gYWRkIGEgc2VyaWVzIG9mIG11dGF0ZSBzdGF0ZW1lbnRzIGRlc2lnbmVkIHRvIGNhdGNoIGVycm9uZW91cyBwdWJsaXNoL2V4cGlyYXRpb24gZGF0ZXMgaW1wdXRlZCBpbnRvIGFuIG9sZCBkYXRhIHJvdXRpbmUgKHRoZXNlIGRhdGEgd2VyZSB0aGUgb25lcyBiZWluZyB1c2VkIGZvciBtYW51YWwgZ2VvY29kaW5nKS4gVGhpcyBpcyBlc3NlbnRpYWxseSBhIHNlcmllcyBvZiByZXBsYWNlbWVudHMgZm9yIG9sZCBpbXB1dGVkIHZhbHVlcyB1c2luZyBgaWZlbHNlYCBzdGF0ZW1lbnRzIHRvIGNhdGNoIGFuZCBvdmVyLXdyaXRlIGluY29ycmVjdCBkYXRlcy4NCg0KTm9yYSBKb2huc3RvbiBkaWQgdGhpcyBtYW51YWxseSBiYXNlZCBvbiBhbiBleHBvcnQgb2YgYGVycm9yc19uamAsIGFuZCB0aGVyZSBhcmUgYSBoYW5kZnVsIG9mIGNvcnJlY3Rpb25zIHdoaWNoIEkgZGlkIGxhdGVyIHdoZXJlIEkgdXNlIGBtdXRhdGVgIGNvbW1hbmRlcyB0byBhbHRlciB0aGUgbGF0L2xvbiBpbmZvLg0KDQpgYGB7ciBmaXhfbmpfZ2VvY29kZXMyLCBjYWNoZSA9IFRSVUUsIGV2YWwgPSBGQUxTRX0NCmdlb2NvZGVkX25qX2ZpeGVkIDwtICBnZW9jb2RlZF9uaiAlPiUgDQogIGFudGlfam9pbiguLCBlcnJvcnNfbmosIGJ5ID0gImFjY291bnQiKSAlPiUNCiAgcmJpbmQoLiwgcmVhZC5jc3YoIi8vam92ZS5kZXNpZ24udXBlbm4uZWR1L0RlcHQtU2hhcmVzL3ByYXgvMDEgUHJvamVjdCBGb2xkZXJzLzIwMTlfQW5uZW5iZXJnX0dlb1NjYW5uaW5nL2lucHV0RGF0YS9SZXRhaWxlck1hcHMvUmV0YWlsZXJNYXBzL3JlcGFpcmVkR2VvY29kZXMvZXJyb3JzX25qXzZfOV8yMF9tZl9lZGl0cy5jc3YiKSAlPiUNCiAgZHBseXI6OnNlbGVjdChjYW5vbmljYWxfbmFtZXMpICU+JQ0KICBtdXRhdGVfaWYoaXMuZmFjdG9yLCBhcy5jaGFyYWN0ZXIpICU+JQ0KICBtdXRhdGUoZXhwaXJhdGlvbl9kYXRlID0gbWR5KGV4cGlyYXRpb25fZGF0ZSksDQogICAgICAgICBwdWJsaXNoX2RhdGUgPSBtZHkocHVibGlzaF9kYXRlKSkpICU+JQ0KICBtdXRhdGUocHVibGlzaF9kYXRlID0gYXMuY2hhcmFjdGVyKHB1Ymxpc2hfZGF0ZSksIA0KICAgICAgICAgZXhwaXJhdGlvbl9kYXRlID0gYXMuY2hhcmFjdGVyKGV4cGlyYXRpb25fZGF0ZSksIA0KICAgICAgICAgcHVibGlzaF9kYXRlID0gaWZlbHNlKHB1Ymxpc2hfZGF0ZSA9PSAnMjAxOC0wMy0wOCcsICcyMDE4LTA0LTAxJywgcHVibGlzaF9kYXRlKSwgDQogICAgICAgICBwdWJsaXNoX2RhdGUgPSBpZmVsc2UocHVibGlzaF9kYXRlID09ICcyMDE5LTA4LTAxJywgJzIwMTktMDQtMDEnLCBwdWJsaXNoX2RhdGUpLCANCiAgICAgICAgIGV4cGlyYXRpb25fZGF0ZSA9IGlmZWxzZShleHBpcmF0aW9uX2RhdGUgPT0gJzIwMTktMDMtMDcnLCcyMDE5LTAzLTMxJywgZXhwaXJhdGlvbl9kYXRlKSwgDQogICAgICAgICBleHBpcmF0aW9uX2RhdGUgPSBpZmVsc2UoZXhwaXJhdGlvbl9kYXRlID09ICcyMDIwLTA3LTMxJywgJzIwMjAtMDMtMzEnLCBleHBpcmF0aW9uX2RhdGUpLCANCiAgICAgICAgIHB1Ymxpc2hfZGF0ZSA9IHltZChwdWJsaXNoX2RhdGUpLCANCiAgICAgICAgIGV4cGlyYXRpb25fZGF0ZSA9IHltZChleHBpcmF0aW9uX2RhdGUpKSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICJOSl8xOV8xNzk0IiwgNDAuMzc5NTkyLCBsYXQpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIk5KXzE5XzE3OTQiLCAtNzQuNTgwMjQ1LCBsb24pKSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICJOSl8xOV83OTciLCA0MC44NjQ2NzIsICAgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICJOSl8xOV83OTciLCAtNzMuOTYxMjUzLCBsb24pKSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICJOSl8xOF8yMzM5IiwgNDAuNzQxMTI4LCAgIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiTkpfMThfMjMzOSIsIC03NC41MzU4MjgsIGxvbikpDQpgYGANCg0KDQojIyA1LjguIFdyaXRlIG91dCB0aGUgZGF0YQ0KDQpUaGlzIGlzIG91ciBmaW5hbCBOSiBkYXRhIHNldCAtIGBnZW9jb2RlZF9ual9maXhlZGAuIEl0IGNhbiBiZSB3cml0dGVuIG91dCB0byBmaWxlLiBXZSB3aWxsIGJpbmQgaXQgdG8gb3VyIERFIGFuZCBQQSBkYXRhIGxhdGVyIGluIHRoZSBjb2RlIGJhc2UuDQoNCmBgYHtyIGV4cG9ydF9uaiwgd2FybmluZz0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBUUlVFLCBldmFsID0gRkFMU0V9DQp3cml0ZS5jc3YoZ2VvY29kZWRfbmpfZml4ZWQsICIvL2pvdmUuZGVzaWduLnVwZW5uLmVkdS9EZXB0LVNoYXJlcy9wcmF4LzAxIFByb2plY3QgRm9sZGVycy8yMDE5X0FubmVuYmVyZ19HZW9TY2FubmluZy9kYXRhT3V0cHV0cy9ual9nZW9jb2RlZF83XzIwXzIwLmNzdiIpDQpgYGANCg0KIyA2LiBSb3V0aW5lIGZvciBOZXcgTkogRGF0YSAoRXhwZWN0ZWQgQXVndXN0LCAyMDIwKQ0KDQpQcmVzdW1hYmx5LCBOZXcgSmVyc2V5IHdpbGwgYmUgcmVsZWFzaW5nIG5ldyBkYXRhIGluIEF1Z3VzdCwgMjAyMC4gQmVjYXVzZSB0aGVpciBkYXRhIHNldHMgaGF2ZSBiZWVuIHNvbWV3aGF0IG5vbi1zdGFuZGFyZCB0byB0aGlzIHBvaW50LCB3aGF0IGZvbGxvd3MgaXMgYSAqcG9zc2libGUqIHdvcmtmbG93IHRvIGludGFrZSBhbmQgY2xlYW4gdGhlIG5ldyBOSiBkYXRhLCBwcmVzdW1pbmcgdGhlIHN0YW5kYXJkcyBhcmUgZXhhY3RseSB0aGUgc2FtZSBvZiBsYXN0IHllYXIuDQoNCiMjIDYuMS4gTG9hZCBCYXNlbGluZSBkYXRhDQoNClRoaXMgaXMgb3VyIHN0YW5kYXJkIGxvYWRpbmcgcHJvY2VkdXJlLCB3aGljaCBrZWVwcyBvbmx5IHRoZSByZWxldmFudCBjb2x1bW4gbmFtZXMgYW5kIG1ha2VzIHN1cmUgZGF0ZXMgYW5kIGNoYXJhY3RlciBmb3JtYXRzIGFyZSBjb3JyZWN0LiBJZiB0aGlzIGNyZWF0ZXMgYW4gZXJyb3IsIHRoZSBkYXRhIG1heSBoYXZlIGJlZW4gZGlzdHVyYmVkLg0KDQpgYGB7ciBsb2FkX05KX2Jhc2VsaW5lLCBldmFsID0gRkFMU0UsIGNhY2hlID0gVFJVRX0NCnN0b3JlZF9uaiA8LSByZWFkLmNzdigiZmlsZXBhdGggZ29lcyBoZXJlIikgJT4lDQogIGRwbHlyOjpzZWxlY3QoY2Fub25pY2FsX25hbWVzKSAlPiUNCiAgbXV0YXRlX2lmKGlzLmZhY3RvciwgYXMuY2hhcmFjdGVyKSAlPiUNCiAgbXV0YXRlKGV4cGlyYXRpb25fZGF0ZSA9IHltZChleHBpcmF0aW9uX2RhdGUpLA0KICAgICAgICAgcHVibGlzaF9kYXRlID0geW1kKHB1Ymxpc2hfZGF0ZSkpDQpgYGANCiMjIDYuMi4gTG9hZCBOZXcgRGF0YQ0KDQpXZSBsb2FkIG5ldyBkYXRhIHByZXN1bWluZyBzYW1lIGRhdGEgc3BlY2lmaWNhdGlvbnMgYXMgQXVndXN0IDIwMTkgZGF0YSAoc2FtZSBjb2x1bW5zIGFuZCBkYXRhIHR5cGVzLikuDQoNClRoaXMgaXMgYSBzcG90IHdoZXJlIHRoZXJlIGlzIHBvdGVudGlhbCB0cm91YmxlLg0KDQpPbmUgd2lsbCBuZWVkIHRvIHNwZWNpZnkgdGhlIGZpbGVwYXRoIGFuZCBpbiB0aGUgZXZlbnQgdGhlc2UgZXhjZWwgZGF0YSBhcmUgbm90IGZvcm1hdHRlZCB0aGUgc2FtZSBhcyBsYXN0IHllYXIsIGFsdGVyIHRoZSBgc2tpcGAgY29tbWFuZCBpbiB0aGUgYHJlYWRfZXhjZWxgIGZ1bmN0aW9uLiBgc2tpcGAgc3BlY2lmaWVzIGhvdyBtYW55IHJvd3MgdG8gc2tpcCBvdmVyIGJlZm9yZSBmaW5kaW5nIHRoZSBjb2x1bW4gaGVhZGVycy4gTG9vayBvdmVyIHRoZSBkYXRhIHNldCBpbiBleGNlbCB0byBzZWUgaG93IGl0IGxvb2tzLg0KDQpJZiBjb2x1bW4gbmFtZXMgYXJlIGRpZmZlcmVudCBmcm9tIHRoZSBhc3N1bWVkIGZvcm1hdCwgdXNpbmcgYHJlbmFtZWAgY29tbWFuZHMgaW4gdGhpcyBmdW5jdGlvbiBvciBjaGFuZ2luZyBjb2x1bW4gbmFtZXMgaW4gdGhlIGV4Y2VsIHNoZWV0IHNob3VsZCBzb2x2ZSB0aGUgcHJvYmxlbS4NCg0KYGBge3IgbG9hZF9OSl8zLCBjYWNoZSA9IFRSVUUsIGV2YWwgPSBGQUxTRX0NCnJldGFpbGVyc19uZXdfTkogPC0gcmVhZF9leGNlbCgiLy9qb3ZlLmRlc2lnbi51cGVubi5lZHUvRGVwdC1TaGFyZXMvcHJheC8wMSBQcm9qZWN0IEZvbGRlcnMvMjAxOV9Bbm5lbmJlcmdfR2VvU2Nhbm5pbmcvaW5wdXREYXRhL1JldGFpbGVyTWFwcy9SZXRhaWxlck1hcHMvTkogKG5jb29wZXJAYXNjLnVwZW5uLmVkdSkvTkogQ2lnYXJldHRlIFJldGFpbGVycyAyMDIwIChPUFJBIEMxNjIzMzIpICgxKS54bHN4Iiwgc2tpcCA9IDUpICAlPiUNCiAgbXV0YXRlKFppcCA9IGlmZWxzZShzdHJfbGVuZ3RoKFppcCkgPT0gOSwgc3RyX3N1YihaaXAsIGVuZCA9IC01KSwgWmlwKSklPiUNCiAgdW5pdGUoLiwgImFkZHJlc3NfZnVsbCIsIHNlcCA9ICIsICIsDQogICAgICAgIGMoIlN0cmVldCIsICJDaXR5IiwgIlN0YXRlIiwgIlppcCIpKSAlPiUNCiAgbXV0YXRlKGFkZHJlc3NfZnVsbCA9IHN0cl9yZXBsYWNlKGFkZHJlc3NfZnVsbCwgIiYiLCAiQU5EIikpJT4lDQogIG11dGF0ZShleHBpcmVkX3lfbiA9ICJBQ1RJVkUiLCANCiAgICAgICAgIGV4cGlyYXRpb25fZGF0ZSA9IHltZCgiMjAyMS0wMy0zMSIpLCAgIyBpcyB0aGlzIGNvcnJlY3Q/DQogICAgICAgICBwdWJsaXNoX2RhdGUgPSB5bWQoIjIwMjAtMDQtMDEiKSwNCiAgICAgICAgIGFjY291bnQgPSBzdHJfYygiTkpfMjAiLCByb3dfbnVtYmVyKCksIHNlcCA9ICJfIiksIA0KICAgICAgICAgbGljZW5zZV90eXBlID0gTkEsIGNvdW50eSA9IE5BLA0KICAgICAgICAgc3RhdGUgPSAiTkoiLA0KICAgICAgICAgVHJhZGUgPSBpZmVsc2UoaXMubmEoVHJhZGUpID09IFRSVUUsIE5hbWUsIFRyYWRlKSkgJT4lIA0KICAgIHJlbmFtZSh0cmFkZV9uYW1lID0gVHJhZGUpICU+JSANCiAgICBkcGx5cjo6c2VsZWN0KC1Mb2NfQ29kZSwgLU5hbWUpDQpgYGANCg0KIyMgNi4zLiBDb2FsZXNjZSBEYXRhIFNldHMNCg0KQ2hlY2sgdG8gc2VlIHdoaWNoIGxpY2Vuc2VzIGFyZSBuZXcuIElmIHRoZXkgYXJlIG9sZCwgd2UgY2FuIHRocm93IHRoZW0gb3V0IGFuZCB3aGVuIHdlIGFkZCB0aGUgbmV3IGRhdGEsIHdlIHdpbGwganVzdCB1cGRhdGUgdGhlaXIgZGF0ZSBvZiBleHBpcnkuIElmIGRhdGEgaW4gdGhlIG9sZCBkYXRhYmFzZSBhcmUgbm90IHJlcHJlc2VudGVkIGluIHRoZSBuZXcgb25lLCB3ZSBrZWVwIHRoZW0gaW4sIGJ1dCBtYXJrIHRoZW0gYXMgZXhwaXJlZC4NCg0KTm90ZSB0aGUgdXNlIG9mIHRoZSBgZHBseXJgIHZlcnNpb24gb2YgYGlmZWxzZWAgY2FsbGVkIGBpZl9lbHNlYCBoZXJlIGluIG9yZGVyIHRvIG1ha2Ugc3VyZSB0aGUgcmVzdWx0IGlzIGEgZGF0ZSBhbmQgbm90IG51bWVyaWMuDQoNCkhlcmUncyB3aGF0IHRoaXMgZG9lczoNCg0KMS4gIEZ1bGwgSm9pbiBvZiByZXRhaWxlcnMgZnJvbSBiYXNlbGluZSBOSiBkYXRhIChgc3RvcmVkX25qYCkgdG8gbmV3IGRhdGEgKGByZXRhaWxlcnNfbmV3X05KYCksIGpvaW5lZCBieSBib3RoIGFjY291bnQgYW5kIHRyYWRlIG5hbWUuIFNpbmNlIHRoZSBkYXRhIHNldHMgaGF2ZSBpZGVudGljYWwgbmFtZXMsIHRoaXMgcHJvZHVjZXMgYW4gIngiIGFuZCAieSIgY29sdW1uIGZvciBlYWNoIG9mIG91ciBjYW5vbmljYWwgbmFtZXMgLSB4IGJlaW5nIHRoZSBsZWZ0IHNpZGUgYW5kIHkgYmVpbmcgdGhlIHJpZ2h0IHNpZGUgb2YgdGhlIGpvaW4uDQoNCjIuICBJZiB0aGVyZSBpcyBubyBleHBpcmF0aW9uIGRhdGUgb24gZmlsZSBmb3IgdGhlIG5ldyBzZXQgKGUuZy4gdGhlcmUgaXMgbm8gZW50cnkgZm9yIGEgcmV0YWlsZXIgb24gZmlsZSBpbiB0aGUgbmV3ZXN0IGRhdGEgc2V0KSwgYXNzdW1lIHRoZSBhY2NvdW50IGlzIGV4cGlyZWQgLSBtYXJrIGl0ICJFWFBJUkVEIiBvciBlbHNlICJBQ1RJVkUiDQoNCjMuIElmIGl0J3MgZXhwaXJlZCAoZS5nLiB0aGVyZSBpcyBubyBjb3JyZXNwb25kaW5nIHkgZW50cnkgZm9yIGEgZ2l2ZW4geCBlbnRyeSksIG1hcmsgYW4gZXhwaXJhdGlvbiBkYXRlIGFzIGJlaW5nIHRoZSBsYXN0IGFjdGl2ZSBkYXRlIG9mIHRoZSBsaWNlbnNlIG9uIGZpbGUgZnJvbSB0aGUgZXhpc3RpbmcgZGF0YWJhc2UuDQoNCjQuIENyZWF0ZSBhIG5ldyBzZXQgb2YgY2Fub25pY2FsIGNvbHVtbnMgYnkgY2hlY2tpbmcgdG8gc2VlIGlmIGJvdGggeCBhbmQgeSBjb2x1bW5zIGFyZSBmaWxsZWQgZm9yIGVhY2ggb2Ygb3VyIHZhcmlhYmxlcyAtIGlmIHRoZXJlJ3Mgbm8geCwgcHV0IHRoZSB5IGRhdGEgaW4gdGhlcmUgYW5kIHZpY2UgdmVyc2EuDQoNCjUuIFVzZSBzaW1pbGFyIGxvZ2ljIHRvIGZpbGwgaW4gYWRkcmVzcywgY291bnR5LCBwdWJsaXNoIGRhdGUgYW5kIGxpY2Vuc2UgdHlwZSBkYXRhLg0KDQo2LiBSZW1vdmUgdGhlIGNvbHVtbnMgd2hpY2ggYXJlIGxlZnQgb3ZlciBmcm9tIHRoZSBqb2luIChlLmcuIGBsYXQueGAsIGBsaWNlbnNlX3R5cGUueWApIHVzaW5nIGBkcGx5cjo6c2VsZWN0YA0KDQpJbiB0aGlzIGpvaW4gKnRoZSBtb3N0IHJlY2VudCBkYXRhIHNldCBnb2VzIHNlY29uZCAoZS5nLiB0aGUgInkiKSBkYXRhIHNldC4qDQoNCg0KYGBge3IgY29hbGVzY2VfTkpfMiwgY2FjaGUgPSBUUlVFLCBldmFsID0gRkFMU0V9DQpOSl9qb2luIDwtIGZ1bGxfam9pbihzdG9yZWRfbmosIHJldGFpbGVyc19uZXdfTkosIGJ5ID0gImFkZHJlc3NfZnVsbCIpICU+JQ0KbXV0YXRlKGV4cGlyZWRfeV9uID0gaWZlbHNlKGlzLm5hKGV4cGlyYXRpb25fZGF0ZS55KSA9PSBUUlVFLCAiRVhQSVJFRCIsICJBQ1RJVkUiKSwNCiAgICAgICAgIGV4cGlyYXRpb25fZGF0ZSA9IGlmX2Vsc2UoZXhwaXJlZF95X24gPT0gJ0VYUElSRUQnLCBleHBpcmF0aW9uX2RhdGUueCwgeW1kKGV4cGlyYXRpb25fZGF0ZS55KSksDQogICAgICAgICBjb3VudHkgPSBpZmVsc2UoaXMubmEoY291bnR5LnkpID09IFRSVUUsIGNvdW50eS54LCBjb3VudHkueSksDQogICAgICAgICBwdWJsaXNoX2RhdGUgPSBpZl9lbHNlKGlzLm5hKHB1Ymxpc2hfZGF0ZS55KSA9PSBUUlVFLCBwdWJsaXNoX2RhdGUueCwgcHVibGlzaF9kYXRlLnkpLA0KICAgICAgICAgbGljZW5zZV90eXBlID0gaWZlbHNlKGlzLm5hKGxpY2Vuc2VfdHlwZS55KSA9PSBUUlVFLCBsaWNlbnNlX3R5cGUueCwgbGljZW5zZV90eXBlLnkpLA0KICAgICAgICAgYWNjb3VudCA9IGlmZWxzZShpcy5uYShhY2NvdW50LngpID09IEZBTFNFLCBhY2NvdW50LngsIGFjY291bnQueSksDQogICAgICAgICB0cmFkZV9uYW1lID0gaWZlbHNlKGlzLm5hKHRyYWRlX25hbWUueSkgPT0gRkFMU0UsIHRyYWRlX25hbWUueSwgdHJhZGVfbmFtZS54KSwNCiAgICAgICAgIGV4cGlyYXRpb25fZGF0ZSA9IGlmX2Vsc2UoaXMubmEoZXhwaXJhdGlvbl9kYXRlLnkpID09IEZBTFNFLCBleHBpcmF0aW9uX2RhdGUueSwgZXhwaXJhdGlvbl9kYXRlLngpLA0KICAgICAgICAgZXhwaXJhdGlvbl9kYXRlID0gaWZfZWxzZShpcy5uYShleHBpcmF0aW9uX2RhdGUueSkgPT0gRkFMU0UsIGV4cGlyYXRpb25fZGF0ZS55LCBleHBpcmF0aW9uX2RhdGUueCksDQogICAgICAgICBwdWJsaXNoX2RhdGUgPSBpZl9lbHNlKGlzLm5hKHB1Ymxpc2hfZGF0ZS54KSA9PSBGQUxTRSwgcHVibGlzaF9kYXRlLngsIHB1Ymxpc2hfZGF0ZS55KSwNCiAgICAgICAgIHN0YXRlID0gIk5KIiwNCiAgICAgICAgIGNvdW50eSA9IE5BKSAlPiUNCiAgc2VsZWN0KC1zdGF0ZS54LCAtIHN0YXRlLnksIC1jb3VudHkueSwgLWNvdW50eS54LCAtYWNjb3VudC54LCAtYWNjb3VudC55LCAtdHJhZGVfbmFtZS55LCAtdHJhZGVfbmFtZS54LA0KICAgICAgICAgLWV4cGlyZWRfeV9uLngsIC1leHBpcmVkX3lfbi55LCAtZXhwaXJhdGlvbl9kYXRlLnksIC1leHBpcmF0aW9uX2RhdGUueCwgLWxpY2Vuc2VfdHlwZS54LCAtbGljZW5zZV90eXBlLnksDQogICAgICAgICAtcHVibGlzaF9kYXRlLngsIC1wdWJsaXNoX2RhdGUueSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoY2Fub25pY2FsX25hbWVzKQ0KYGBgDQoNCiMjIDYuNC4gR2VvY29kZSBtaXNzaW5nIG9ic2VydmF0aW9ucw0KDQoNCk5vdyB3ZSBnZW9jb2RlIHRoZSBvYnNlcnZhdGlvbnMgd2hpY2ggbGFjayBhIGxhdC9sb24gKGB0b19nZW9jb2RlX25qYCkgYnkgc2VuZGluZyBhZGRyZXNzZXMgdG8gdGhlIEdvb2dsZSBnZW9jb2RpbmcgZW5naW5lLCBmZXRjaGluZyBhIGxhdC9sb24gcGFpciAoYGdlb2NvZGVgKSBhbmQgdGhlbiBiaW5kaW5nIHRoZSBsYXQvbG9uIGNvbHVtbnMgYmFjayB0byB0aGUgcmVzdCBvZiB0aGUgZGF0YSAoYGNiaW5kYCkgYW5kIGJpbmRpbmcgdGhlIGdlb2NvZGVkIG9ic2VydmF0aW9ucyBiYWNrIHRvIHRob3NlIGRhdGEgd2hpY2ggaGFkIGEgbGF0L2xvbiB2YWx1ZSAoYHJiaW5kYCkuDQoNClRoZSBgc3VtbWFyeWAgZnVuY3Rpb24gd2lsbCB0ZWxsIHVzIGhvdyBtYW55IGdlb2NvZGVzIGhhdmUgYmVlbiB1bnN1Y2Nlc3NmdWwgdXAgdW50aWwgdGhpcyBwb2ludC4NCg0KDQpgYGB7ciBual9yZXRhaWxlcl9uYV9zdW1tYXJ5X2pvaW5fMiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPSBGQUxTRSxjYWNoZT1UUlVFLCBldmFsID0gRkFMU0V9DQp0b19nZW9jb2RlX25qIDwtIE5KX2pvaW4gJT4lDQogIGZpbHRlcihpcy5uYShsYXQpID09IFRSVUUpDQoNCmdlb2NvZGVkX25qIDwtIGdlb2NvZGUodG9fZ2VvY29kZV9uaiRhZGRyZXNzX2Z1bGwsIHNvdXJjZSA9ICAiZ29vZ2xlIikgJT4lDQogIGNiaW5kKC4sIHRvX2dlb2NvZGVfbmogJT4lDQogICAgICAgICAgc2VsZWN0KC1sYXQsIC1sb24pKSAlPiUNCiAgcmJpbmQoTkpfam9pbiAlPiUNCiAgICAgICAgICBmaWx0ZXIoaXMubmEobGF0KSA9PSBGQUxTRSksIC4pDQoNCnN1bW1hcnkoaXMubmEoZ2VvY29kZWRfbmokbGF0KSkNCmBgYA0KDQoNCiMjIDYuNS4gUGVyZm9ybSBkYXRhIHF1YWxpdHkgY2hlY2sgZm9yIGJhZCBnZW9jb2Rlcw0KDQoqVGhpcyBzZWN0aW9uIGlzIHJvdWdobHkgdGhlIHNhbWUgYXMgMy41IC0gdGhlIG1ldGhvZG9sb2d5IGlzIHJlcHJpbnRlZCBoZXJlIHVzaW5nIHRoZSBhcHByb3ByaWF0ZSBvYmplY3QgbmFtZXMqDQoNCldoZW4gZ2VvY29kZXMgYXJlIHdyb25nLCB0aGV5IGFyZSBvZnRlbiB2ZXJ5IHdyb25nLCBhbmQgc2V0IHNvbWV3aGVyZSB3aWxkbHkgZGlmZmVyZW50IGZyb20gb3VyIHRhcmdldCBhcmVhLiBXZSBkbyBhIHNwYXRpYWwgam9pbiB3aXRoIHRoZSBOSiBzaGFwZWZpbGUsIGtlZXAgb25seSB0aGUgb2JzZXJ2YXRpb25zIHdoaWNoIGZhbGwgb3V0c2lkZSBvZiBOSi4gV2UgY2FuIGNoZWNrIHRoZXNlIG9ic2VydmF0aW9ucyBhbmQgcG90ZW50aWFsbHkgZ2VvY29kZSB0aGVtIG1hbnVhbGx5Lg0KDQpXZSBkbyB0aGlzIHRocm91Z2ggdGhlIGZvbGxvd2luZyBzdGVwczoNCg0KMS4gUGVyZm9ybSBhIHNwYXRpYWwgam9pbiB1c2luZyBgc3Rfam9pbmAgZnJvbSB0aGUgYHNmYCBwYWNrYWdlIHRvIHNlZSB3aGljaCBwb2ludHMgZnJvbSBgZ2VvY29kZWRfbmpgIGZhbGwgb3V0c2lkZSBOSiAoZXhjbHVkaW5nIHRob3NlIHdlIGtub3cgYXJlIE5BIGF0IHByZXNlbnQpLiBUbyBkbyB0aGlzIHdlIG5lZWQgdG8gcmVwcm9qZWN0IG91ciBsYXQvbG9uIGRhdGEgYXMgYW4gYHNmYCB2ZWN0b3Igb2JqZWN0IHByb2plY3RlZCBpbiBXR1M4NCAod2ViIG1lcmNhdG9yLCBgY3JzID0gNDMyNmApIGFuZCBqb2luIGl0IHRvIGBual9zaHBgLg0KDQoyLiBXZSBgZmlsdGVyYCB0aG9zZSBvYnNlcnZhdGlvbnMgd2hpY2ggY29tZSBiYWNrIGFzIGBpcy5uYShTVFVTUFMpID09IFRSVUVgIChlLmcuIHRoZXkgZG9uJ3Qgam9pbiBhdCBhbGwgdG8gdGhlIE5KIHRpZ3JpcyBzaGFwZWZpbGUgYW5kIGRvbid0IHRha2Ugb24gYW55IG9mIGl0J3MgYXR0cmlidXRlcyBsaWtlIFNUVVNQUykgYW5kIGtub3cgdGhhdCB0aGVzZSBhcmUgdGhlIG9uZXMgd2hpY2ggZmVsbCBvdXRzaWRlIG91ciBzaGFwZWZpbGUuDQoNCjMuIFdlIGBkcGx5cjo6c2VsZWN0YCBvbmx5IG91ciBjYW5vbmljYWwgdmFyaWFibGVzIGZyb20gdGhlc2UgYW5kIGV4dHJhY3QgdGhlIGxhdC9sb24gZnJvbSB0aGVpciBnZW9tZXRyaWVzIHVzaW5nIGEgYG11dGF0ZWAgc3RhdGVtZW50LiBXZSB3YW50IHRvIHR1cm4gdGhpcyBiYWNrIGludG8gYSBkYXRhZnJhbWUgd2l0aCBsYXQvbG9uIGRhdGEgYW5kIG5vdCBnZW9tZXRyaWVzIGxpa2UgYW4gYHNmYCBvYmplY3QuIFNvIHdlIHNldCBpdCBgYXMuZGF0YS5mcmFtZWAgYW5kIHJlbW92ZSB0aGUgYGdlb21ldHJ5YCBjb2x1bW4uDQoNCjQuIFdlIG5vdyBgcmJpbmRgIHRoZXNlIGRhdGEgYmFjayB0byBvdXIgYGdlb2NvZGVkX25qYCBvYnNlcnZhdGlvbnMgd2hpY2ggbGFja2VkIGxhdC9sb24gaW4gdGhlIGZpcnN0IHBsYWNlLCB0byBjcmVhdGUgYSBkYXRhIHNldCBjb25zaXN0aW5nIG9mIGFsbCBvdXIgb2JzZXJ2YXRpb25zIHdoaWNoIHdlIHByZXN1bWUgaGF2ZSBiYWQgZ2VvY29kZXMuDQoNCg0KYGBge3IgZGV0ZWN0X2Vycm9yc19OSl8yLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBUUlVFLCBldmFsID0gRkFMU0V9DQplcnJvcnNfbmogPC0gc3Rfam9pbihnZW9jb2RlZF9uaiAlPiUgDQogICAgICAgICAgZmlsdGVyKGlzLm5hKGxhdCkgPT0gRkFMU0UpICU+JSANCiAgICAgICAgICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJsb24iLCAibGF0IiksIGNycyA9IDQzMjYpLCANCiAgICAgICAgbmpfc2hwLCANCiAgICAgICAgam9pbiA9IHN0X3dpdGhpbiwgDQogICAgICAgIGxlZnQgPSBUUlVFKSAlPiUNCiAgZmlsdGVyKGlzLm5hKFNUVVNQUykgPT0gVFJVRSkgJT4lDQogIG11dGF0ZShsb249bWFwX2RibChnZW9tZXRyeSwgfnN0X2NlbnRyb2lkKC54KVtbMV1dKSwNCiAgICAgICAgIGxhdD1tYXBfZGJsKGdlb21ldHJ5LCB+c3RfY2VudHJvaWQoLngpW1syXV0pKSU+JQ0KICBkcGx5cjo6c2VsZWN0KGNhbm9uaWNhbF9uYW1lcykgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtZ2VvbWV0cnkpICU+JQ0KICByYmluZCguLCBnZW9jb2RlZF9uaiAlPiUNCiAgICAgICAgICBmaWx0ZXIoaXMubmEobGF0KSA9PSBUUlVFKSU+JQ0KICAgICAgICAgIGRwbHlyOjpzZWxlY3QoY2Fub25pY2FsX25hbWVzKSkNCmBgYA0KDQpXZSBwZXJmb3JtIGEgZGF0YSBjaGVjayB0byBzZWUgaG93IG1hbnkgTkEgb2JzZXJ2YXRpb25zIHJlbWFpbiBpbiBvdXIgZGF0YS4NCg0KDQpgYGB7ciBob3dfbWFueV9uYV8yLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBUUlVFLCBldmFsID0gRkFMU0V9DQoNCnN1bW1hcnkoaXMubmEoZXJyb3JzX25qJGxhdCkpDQoNCm5yb3coZXJyb3JzX25qKQ0KDQpgYGANCg0KIyMgNi42LiBNYW51YWxseSBnZW9jb2RlIG1pc3Npbmcgb2JzZXJ2YXRpb25zDQoNCipUaGlzIHJvdXRpbmUgaXMgc2ltaWxhciB0byBTZWN0aW9uIDQuNiBidXQgaXMgcmUtd3JpdHRlbiBoZXJlIHdpdGggYXBwcm9wcmlhdGUgb2JqZWN0IG5hbWVzKg0KDQpUaGUgb2JzZXJ2YXRpb25zIHRoYXQgZG9uJ3QgZ2VvY29kZSB3aWxsIG5lZWQgdG8gYmUgZGVhbHQgd2l0aCBtYW51YWxseSAoZS5nLiBpbnB1dCBsYXQvbG9uIGNvb3JkaW5hdGVzIGJhc2VkIG9uIGdvb2dsZSBtYXAgcmVzZWFyY2gsIGxpY2Vuc2UgcmVhc2VhcmNoIGV0Yy4sKS4gU2ltaWxhcmx5LCBvYnNlcnZhdGlvbnMgdGhhdCBnZW9jb2RlIHBvb3JseSBhbmQgZW5kIHVwIG9uIG91ciBgZXJyb3JzX25qYCBsaXN0IG5lZWQgdG8gYmUgbWFudWFsbHkgZ2VvY29kZWQuDQoNClRoaXMgaGFzIHRvIGJlIGRvbmUgYnkgaGFuZCB1bmZvcnR1YXRlbHkuIA0KDQpXZSB3cml0ZSBvdXQgYGVycm9yc19uamAgYW5kIHRoZW4gdHlwZSBpbiB0aGUgbGF0L2xvbiBtYW51YWxseSBpbiBFeGNlbC4gVGhpcyBpcyBtdWNoIGVhc2llciB0aGFuIGRvaW5nIGEgc2VyaWVzIG9mIGBtdXRhdGVgIHN0YXRlbWVudHMuDQoNClRoZSBuYW1lIG9mIHRoaXMgZmlsZXBhdGggc2hvdWxkIGNoYW5nZSBlYWNoIHRpbWUgeW91IGRvIGl0IHNvIGFzIG5vdCB0byBvdmVyd3JpdGUgcHJldmlvdXMgd29yay4gQXMgeW91IGFyZSBnZW9jb2RpbmcgdGhlIG9ic2VydmF0aW9ucyBieSBoYW5kLCBrZWVwIHRoZSBmaWxlIGluIGNzdiBmb3JtLCByYXRoZXIgdGhhbiB4bHN4Lg0KDQpgYGB7ciB3cml0ZV9vdXRfbmpfZXJyb3JzMiwgZXZhbCA9IEZBTFNFLCBjYWNoZSA9IFRSVUUsIGV2YWwgPSBGQUxTRX0NCndyaXRlLmNzdihlcnJvcnNfbmosICJmaWxlcGF0aCBnb2VzIGhlcmUiKQ0KYGBgDQoNCk9uY2Ugd2UndmUgZG9uZSBvdXIgZ2VvY29kaW5nIHJlc2VhcmNoLCBhbmQgYWRkIGluIHRoZSBsYXQvbG9uIGRhdGEgd2hpY2ggd2FzIG1pc3NpbmcsIHdlIGxvYWQgdGhlIChub3cgY29ycmVjdCkgZGF0YSBiYWNrIGluIHVzaW5nIGEgYHJlYWQuY3N2YCBjb21tYW5kIGFuZCBqb2luIGl0IHRvIG91ciBwcm9wZXJseSBnZW9jb2RlZCBkYXRhLg0KDQpXZSB1c2UgYW4gYGFudGlfam9pbmAgdG8gZ2VvY29kZWQgdG8gaXNsb2F0ZSB0aG9zZSBvYnNlcnZhdGlvbnMgZnJvbSBgZ2VvY29kZWRfbmpgIHRoYXQgZGlkbid0IGVuZCB1cCBvdXIgYGVycm9yc19uamAgZGF0YSBzZXQsIGFuZCB0aGVuIGJpbmQgdGhlIHJvd3MgdG9nZXRoZXIgd2l0aCB0aGUgbWFudWFsbHkgY29ycmVjdGVkIGVycm9ycy4gTm90ZSB0aGF0IGluc2lkZSB0aGUgYHJiaW5kYCBmdW5jdGlvbiB3ZSBkbyBzb21lIGRhdGEgY2xlYW5pbmcgd2l0aCBvdXIgdXBsb2FkZWQgZXJyb3JzIHNvIHRoYXQgd2Uga2VlcCBvbmx5IHRoZSBjYW5vbmljYWwgbmFtZXMgYW5kIG91ciBjb2x1bW4gZGF0YXR5cGVzIGFyZSBpbiB0aGUgY29ycmVjdCBmb3JtYXRzLg0KDQpgYGB7ciBmaXhfbmpfZ2VvY29kZXMsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0KZ2VvY29kZWRfbmpfZml4ZWQgPC0gIGdlb2NvZGVkX25qICU+JSANCiAgYW50aV9qb2luKC4sIGVycm9yc19uaikgJT4lDQogIHJiaW5kKC4sIHJlYWQuY3N2KCJGSUxFUEFUSCBGT1IgSEFORC1HRU9DT0RFRCBPQlNFUlZBVElPTlMiKSAlPiUNCiAgZHBseXI6OnNlbGVjdChjYW5vbmljYWxfbmFtZXMpICU+JQ0KICBtdXRhdGVfaWYoaXMuZmFjdG9yLCBhcy5jaGFyYWN0ZXIpICU+JQ0KICBtdXRhdGUoZXhwaXJhdGlvbl9kYXRlID0geW1kKGV4cGlyYXRpb25fZGF0ZSksDQogICAgICAgICBwdWJsaXNoX2RhdGUgPSB5bWQocHVibGlzaF9kYXRlKSkpDQoNCmBgYA0KDQoNCiMjIDYuNy4gV3JpdGUgb3V0IHRoZSBkYXRhDQoNClRoaXMgaXMgb3VyIGZpbmFsIE5KIGRhdGEgc2V0IC0gYGdlb2NvZGVkX25qX2ZpeGVkYC4gSXQgY2FuIGJlIHdyaXR0ZW4gb3V0IHRvIGZpbGUuIFdlIHdpbGwgYmluZCBpdCB0byBvdXIgREUgYW5kIFBBIGRhdGEgbGF0ZXIgaW4gdGhlIGNvZGUgYmFzZS4NCg0KYGBge3IgZXhwb3J0X25qMiwgd2FybmluZz0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBUUlVFLCBldmFsID0gRkFMU0V9DQp3cml0ZS5jc3YoZ2VvY29kZWRfbmpfZml4ZWQsICJmaWxlcGF0aCBnb2VzIGhlcmUiKQ0KYGBgDQoNCiMgNy4gUHJlY3Vyc29yIFJvdXRpbmUgLSBCYXNlbGluZSBEZWxhd2FyZSBkYXRhDQoNClRoaXMgcHJlY3Vyc29yIHJvdXRpbmUgd2FzIHVzZWQgdG8gbG9hZCBhbmQgY2xlYW4gdGhlIGZpcnN0IHNldCBvZiBEZWxhd2FyZSBkYXRhIHdlIGNvbGxlY3RlZCAtIGluIE1hcmNoIG9mIDIwMjAuIFRoaXMgaXMgYSBwcmVjdXJzb3IgdG8gb3VyIGNvZGUgcm91dGluZSAoZS5nLiBvdXIgY29kZSB0YWtlcyBpbiBORVcgZGF0YSkuIFRoZSBmb2xsb3dpbmcgc3RlcHMgZG8gbm90IG5lZWQgdG8gYmUgcmUtcnVuLCBhbmQgYWN0IG9ubHkgYXMgZG9jdW1lbnRhdGlvbiBmb3IgdGhlIGJ1aWxkaW5nIG9mIHRoZSBwb3J0aW9uIG9mIG91ciByZXRhaWxlciBkYXRhYmFzZS4NCg0KQ29tZXMgZnJvbSBoZXJlOg0KaHR0cHM6Ly9kYXRhLmRlbGF3YXJlLmdvdi9MaWNlbnNlcy1hbmQtQ2VydGlmaWNhdGlvbnMvRGVsYXdhcmUtQnVzaW5lc3MtTGljZW5zZXMvNXp5Mi1ncmhyDQoNClRoZXJlIGFyZSBtYW55IGxpY2Vuc2UgdHlwZXMgaW4gdGhlc2UgZGF0YSwgYW5kIHRob3NlIHRvYmFjY28gcmVsYXRlZCBpbmNsdWRlIHdob2xlc2FsZXJzIGFuZCBpbnRlcm5ldCByZXRhaWxlcnMNCg0KVGhpcyB3aWxsIG5lZWQgdG8gYmUgZmlsdGVyZWQgKGJ5IGNhdGVnb3J5KSBvbiB0aGUgaW5zdHJ1Y3Rpb25zIG9mIHRoZSB0ZWFtLg0KDQpXZSBzaG91bGQgYWxzbyBmaWx0ZXIgb3V0IHdob2xzYWxlcnMgLSBtYW55IG9mIHRoZXNlIGFyZSBvdXRzaWRlIGRlbGF3YXJlIC0gSSBzdXBwb3NlIG5lZWQgdG8gYmUgcmVnaXN0ZXJlZCB0aGVyZS4NCg0KIyMgNy4xLiBMb2FkIERhdGEgRnJvbSBTb2NyYXRhDQoNCkRhdGEgZnJvbSBNYXJjaCwgMjAyMA0KDQpUaGlzIHJvdXRpbmUgaXMgYmFzaWNhbGx5IHRoZSBzYW1lIGFzIHRoZSBiYXNlbGluZSByb3V0aW5lIGZyb20gUEEsIHdoaWNoIHVzZXMgdGhlIHNhbWUgZ2VuZXJhbCBkYXRhIHN0YW5kYXJkcywgYnV0IGl0IGFwcGxpZXMgdG8gYSBicm9hZCBzZXQgb2YgYnVzaW5lc3MgbGljZW5zZXMgdGhhdCBhcmUgcHVibGlzaGVkIGFzIGEgZ3JvdXAuDQoNClN0ZXBzOg0KDQoxLiBHcmFiIHRoZSBkYXRhIGZyb20gdGhlIERFIFNPREEgQVBJLiAoYHJlYWQuc29jcmF0YWApDQoNCjIuIEZpbHRlciB0byBrZWVwIG9ubHkgZGF0YSBvZiB0aGUgY2F0ZWdvcmllcyBgVE9CQUNDTyBQUk9EVUNUUyBSRVRBSUxFUmAgYW5kIGBSRVRBSUxFUi1UT0JBQ0NPYA0KDQozLiBBZGQgYSB0aW1lIHN0YW1wIHdpdGggdGhlIGRhdGUgb2YgZG93bmxvYWQsIGEgY29sdW1uIGZvciAnc3RhdGUnIGFuZCBjb2VyY2UgdGhlIGxpY2Vuc2UgZXhwaXJhdGlvbiBkYXRlIHRvIGRhdGUgKCd5bWQnKQ0KDQo0LiBSZW5hbWUgcmVsZXZhbnQgY29sdW1ucyB0byBsYXQsIGxvbiwgYWNjb3VudCBhbmQgbGljZW5zZV90eXBlDQoNCjUuIGB1bml0ZWAgYWRkcmVzcyBmaWVsZHMgaW50byBvbmUgYWRkcmVzcyBjb2x1bW4gZm9yIGdlb2NvZGluZw0KDQo2LiBSZW1vdmUgdW4tbmVlZGVkIHZhcmlhYmxlcyB1c2luZyBgc2VsZWN0YA0KDQo3LiBVc2UgYG11dGF0ZV9pZmAgdG8gY29lcmNlIGFsbCBmYWN0b3JzIHRvIGNoYXJhY3Rlcg0KDQo4LiBSZXBsYWNlICImIiBjaGFyYWN0ZXJzIHdpdGggIkFORCIgKGJldHRlciBmb3IgZ2VvY29kaW5nKQ0KDQpgYGB7ciByZWFkX3NvY3JhdGFfZGVfcHJlLCBjYWNoZSA9IFRSVUUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGV2YWwgPSBGQUxTRX0NCg0KcmV0YWlsZXJzX1NvY3JhdGFfZGVfcHJlIDwtIHJlYWQuc29jcmF0YSgiaHR0cHM6Ly9kYXRhLmRlbGF3YXJlLmdvdi9yZXNvdXJjZS81enkyLWdyaHIuanNvbiIpICU+JQ0KICBmaWx0ZXIoc3RyX2RldGVjdChjYXRlZ29yeSwgIlRPQkFDQ08gUFJPRFVDVFMgUkVUQUlMRVIiKSB8IHN0cl9kZXRlY3QoY2F0ZWdvcnksICJSRVRBSUxFUi1UT0JBQ0NPIikpICU+JQ0KICBtdXRhdGUocHVibGlzaF9kYXRlID0geW1kKGN1cnJlbnRfbGljZW5zZV92YWxpZF9mcm9tKSwNCiAgICAgICAgIGV4cGlyYXRpb25fZGF0ZSA9IHltZChjdXJyZW50X2xpY2Vuc2VfdmFsaWRfdG8pLA0KICAgICAgICAgZXhwaXJlZF95X24gPSAiQUNUSVZFIiwNCiAgICAgICAgIHRyYWRlX25hbWUgPSBidXNpbmVzc19uYW1lLA0KICAgICAgICAgY291bnR5ID0gTkEsDQogICAgICAgICBzdGF0ZSA9ICJERSIpICU+JQ0KICByZW5hbWUobGF0ID0gZ2VvY29kZWRfbG9jYXRpb24ubGF0aXR1ZGUsDQogICAgICAgICBsb24gPSBnZW9jb2RlZF9sb2NhdGlvbi5sb25naXR1ZGUsDQogICAgICAgICBhY2NvdW50ID0gbGljZW5zZV9udW1iZXIsDQogICAgICAgICBsaWNlbnNlX3R5cGUgPSBjYXRlZ29yeSkgJT4lDQogIG11dGF0ZSh6aXAgPSBpZmVsc2Uoc3RyX2xlbmd0aCh6aXApID09IDksIHN0cl9zdWIoemlwLCBlbmQgPSAtNSksIHppcCkpICU+JQ0KICB1bml0ZSguLCAiYWRkcmVzc19mdWxsIiwgc2VwID0gIiAiLA0KICAgICAgICBjKCJhZGRyZXNzXzIiLCAiY2l0eSIsICJzdGF0ZSIsICJ6aXAiKSkgJT4lDQogIG11dGF0ZShzdGF0ZSA9ICJERSIpICU+JQ0KICBkcGx5cjo6c2VsZWN0KC1idXNpbmVzc19uYW1lLCAtY3VycmVudF9saWNlbnNlX3ZhbGlkX2Zyb20sIA0KICAgICAgICAgICAgICAgIC1jdXJyZW50X2xpY2Vuc2VfdmFsaWRfdG8sIC1jb3VudHJ5LCAtZ2VvY29kZWRfbG9jYXRpb24uaHVtYW5fYWRkcmVzcywNCiAgICAgICAgICAgICAgICAtYWRkcmVzc18xKSAlPiUNCiAgbXV0YXRlX2lmKGlzLmZhY3RvciwgYXMuY2hhcmFjdGVyKSAlPiUNCiAgbXV0YXRlKGFkZHJlc3NfZnVsbCA9IHN0cl9yZXBsYWNlKGFkZHJlc3NfZnVsbCwgIiYiLCAiQU5EIikpDQpgYGANCg0KVXNlIHRoZSBgc3VtbWFyeWAgY29tbWFuZCB0byBleHBsb3JlIE5BIGRhdGENCg0KYGBge3IgaG93X21hbnlfbmFfZGVfcHJlMiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0KDQpzdW1tYXJ5KGlzLm5hKHJldGFpbGVyc19Tb2NyYXRhX2RlX3ByZSRsYXQpKQ0KDQpgYGANCg0KIyMgNy4yLiBHZW9jb2RlIGRhdGENCg0KTm93IHdlIGdlb2NvZGUgdGhlIG9ic2VydmF0aW9ucyB3aGljaCBsYWNrIGEgbGF0L2xvbiAoYHRvX2dlb2NvZGVfZGVfcHJlYCkgYnkgc2VuZGluZyBhZGRyZXNzZXMgdG8gdGhlIEdvb2dsZSBnZW9jb2RpbmcgZW5naW5lLCBmZXRjaGluZyBhIGxhdC9sb24gcGFpciAoYGdlb2NvZGVgKSBhbmQgdGhlbiBiaW5kaW5nIHRoZSBsYXQvbG9uIGNvbHVtbnMgYmFjayB0byB0aGUgcmVzdCBvZiB0aGUgZGF0YSAoYGNiaW5kYCkgYW5kIGJpbmRpbmcgdGhlIGdlb2NvZGVkIG9ic2VydmF0aW9ucyBiYWNrIHRvIHRob3NlIGRhdGEgd2hpY2ggaGFkIGEgbGF0L2xvbiB2YWx1ZSAoYHJiaW5kYCkuDQoNClRoZSBgc3VtbWFyeWAgZnVuY3Rpb24gd2lsbCB0ZWxsIHVzIGhvdyBtYW55IGdlb2NvZGVzIGhhdmUgYmVlbiB1bnN1Y2Nlc3NmdWwgdXAgdW50aWwgdGhpcyBwb2ludC4NCg0KYGBge3IgZGVfcmV0YWlsZXJfbmFfc3VtbWFyeV9qb2luX2RlX3ByZSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPSBGQUxTRSxjYWNoZT1UUlVFLCBldmFsID0gRkFMU0V9DQp0b19nZW9jb2RlX2RlX3ByZSA8LSByZXRhaWxlcnNfU29jcmF0YV9kZV9wcmUgJT4lDQogIGZpbHRlcihpcy5uYShsYXQpID09IFRSVUUpDQoNCmRlX2dlb2NvZGVkX3ByZSA8LSBnZW9jb2RlKHRvX2dlb2NvZGVfZGVfcHJlJGFkZHJlc3NfZnVsbCwgc291cmNlID0gICJnb29nbGUiKSAlPiUNCiAgY2JpbmQoLiwgdG9fZ2VvY29kZV9kZSAlPiUNCiAgICAgICAgICBkcGx5cjo6c2VsZWN0KC1sYXQsIC1sb24pKSAlPiUNCiAgcmJpbmQocmV0YWlsZXJzX1NvY3JhdGFfZGVfcHJlICU+JQ0KICAgICAgICAgIGZpbHRlcihpcy5uYShsYXQpID09IEZBTFNFKSwgLikNCg0Kc3VtbWFyeShpcy5uYShkZV9nZW9jb2RlZF9wcmUkbGF0KSkNCmBgYA0KDQoNCiMjIDcuMy4gUGVyZm9ybSBkYXRhIHF1YWxpdHkgY2hlY2sgZm9yIGJhZCBnZW9jb2Rlcw0KDQpPbmUgc3RyYW5nZSBpc3N1ZSB3aXRoIERFIGRhdGEgaXMgdGhhdCB0aGVyZSBhcmUgb2JzZXJ2YXRpb25zIGluIHRoZXJlIHRoYXQgYXJlIGFic29sdXRlbHkgb3V0c2lkZSBEZWxhd2FyZSAtIHRoZSBsaWNlbnNlcyB0aGVtc2VsdmVzIHNheSBzby4gV2UgY2xlYW4gdGhlc2Ugb3V0IGxhdGVyLCBidXQgdGhleSBvYnZpb3VzbHkgdHJpcCB1cCB0aGUgZm9sbG93aW5nIHJvdXRpbmUuDQoNCldoZW4gZ2VvY29kZXMgYXJlIHdyb25nLCB0aGV5IGFyZSBvZnRlbiB2ZXJ5IHdyb25nLCBhbmQgc2V0IHNvbWV3aGVyZSB3aWxkbHkgZGlmZmVyZW50IGZyb20gb3VyIHRhcmdldCBhcmVhLiBXZSBkbyBhIHNwYXRpYWwgam9pbiB3aXRoIHRoZSBERSBzaGFwZWZpbGUsIGtlZXAgb25seSB0aGUgb2JzZXJ2YXRpb25zIHdoaWNoIGZhbGwgb3V0c2lkZSBvZiBERS4gV2UgY2FuIGNoZWNrIHRoZXNlIG9ic2VydmF0aW9ucyBhbmQgcG90ZW50aWFsbHkgZ2VvY29kZSB0aGVtIG1hbnVhbGx5Lg0KDQpXZSBkbyB0aGlzIHRocm91Z2ggdGhlIGZvbGxvd2luZyBzdGVwczoNCg0KMS4gUGVyZm9ybSBhIHNwYXRpYWwgam9pbiB1c2luZyBgc3Rfam9pbmAgZnJvbSB0aGUgYHNmYCBwYWNrYWdlIHRvIHNlZSB3aGljaCBwb2ludHMgZnJvbSBgZGVfZ2VvY29kZWRfcHJlYCBmYWxsIG91dHNpZGUgREUgKGV4Y2x1ZGluZyB0aG9zZSB3ZSBrbm93IGFyZSBOQSBhdCBwcmVzZW50KS4gVG8gZG8gdGhpcyB3ZSBuZWVkIHRvIHJlcHJvamVjdCBvdXIgbGF0L2xvbiBkYXRhIGFzIGFuIGBzZmAgdmVjdG9yIG9iamVjdCBwcm9qZWN0ZWQgaW4gV0dTODQgKHdlYiBtZXJjYXRvciwgYGNycyA9IDQzMjZgKSBhbmQgam9pbiBpdCB0byBgZGVfc2hwYC4NCg0KMi4gV2UgYGZpbHRlcmAgdGhvc2Ugb2JzZXJ2YXRpb25zIHdoaWNoIGNvbWUgYmFjayBhcyBgaXMubmEoU1RVU1BTKSA9PSBUUlVFYCAoZS5nLiB0aGV5IGRvbid0IGpvaW4gYXQgYWxsIHRvIHRoZSBERSB0aWdyaXMgc2hhcGVmaWxlIGFuZCBkb24ndCB0YWtlIG9uIGFueSBvZiBpdCdzIGF0dHJpYnV0ZXMgbGlrZSBTVFVTUFMpIGFuZCBrbm93IHRoYXQgdGhlc2UgYXJlIHRoZSBvbmVzIHdoaWNoIGZlbGwgb3V0c2lkZSBvdXIgc2hhcGVmaWxlLg0KDQozLiBXZSBgZHBseXI6OnNlbGVjdGAgb25seSBvdXIgY2Fub25pY2FsIHZhcmlhYmxlcyBmcm9tIHRoZXNlIGFuZCBleHRyYWN0IHRoZSBsYXQvbG9uIGZyb20gdGhlaXIgZ2VvbWV0cmllcyB1c2luZyBhIGBtdXRhdGVgIHN0YXRlbWVudC4gV2Ugd2FudCB0byB0dXJuIHRoaXMgYmFjayBpbnRvIGEgZGF0YWZyYW1lIHdpdGggbGF0L2xvbiBkYXRhIGFuZCBub3QgZ2VvbWV0cmllcyBsaWtlIGFuIGBzZmAgb2JqZWN0LiBTbyB3ZSBzZXQgaXQgYGFzLmRhdGEuZnJhbWVgIGFuZCByZW1vdmUgdGhlIGBnZW9tZXRyeWAgY29sdW1uLg0KDQo0LiBXZSBub3cgYHJiaW5kYCB0aGVzZSBkYXRhIGJhY2sgdG8gb3VyIGBkZV9nZW9jb2RlZF9wcmVgIG9ic2VydmF0aW9ucyB3aGljaCBsYWNrZWQgbGF0L2xvbiBpbiB0aGUgZmlyc3QgcGxhY2UsIHRvIGNyZWF0ZSBhIGRhdGEgc2V0IGNvbnNpc3Rpbmcgb2YgYWxsIG91ciBvYnNlcnZhdGlvbnMgd2hpY2ggd2UgcHJlc3VtZSBoYXZlIGJhZCBnZW9jb2Rlcy4NCg0KdGhlcmUgYXJlIExPVFMgb2YgYWRkcmVzc2VzIHdoaWNoIGFyZSBsZWdpdGltYXRlbHkgb3V0c2lkZSBkZWxhd2FyZSAoY2hlY2sgdGhlIHppcCBjb2RlcywgdGhleSBhcmUgaW4gb3RoZXIgc3RhdGVzKQ0KDQpgYGB7ciBkZXRlY3RfZXJyb3JzX2RlX3ByZSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0KZXJyb3JzX2RlX3ByZSA8LSBzdF9qb2luKGRlX2dlb2NvZGVkX3ByZSAlPiUgDQogICAgICAgICAgZmlsdGVyKGlzLm5hKGxhdCkgPT0gRkFMU0UpICU+JSANCiAgICAgICAgICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJsb24iLCAibGF0IiksIGNycyA9IDQzMjYpLCANCiAgICAgICAgZGVfc2hwLCANCiAgICAgICAgam9pbiA9IHN0X3dpdGhpbiwgDQogICAgICAgIGxlZnQgPSBUUlVFKSAlPiUNCiAgZmlsdGVyKGlzLm5hKFNUVVNQUykgPT0gVFJVRSkgJT4lDQogIG11dGF0ZShsb249bWFwX2RibChnZW9tZXRyeSwgfnN0X2NlbnRyb2lkKC54KVtbMV1dKSwNCiAgICAgICAgIGxhdD1tYXBfZGJsKGdlb21ldHJ5LCB+c3RfY2VudHJvaWQoLngpW1syXV0pKSU+JQ0KICBkcGx5cjo6c2VsZWN0KGNhbm9uaWNhbF9uYW1lcykgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtZ2VvbWV0cnkpICU+JQ0KICByYmluZCguLCBkZV9nZW9jb2RlZF9wcmUgJT4lDQogICAgICAgICAgZmlsdGVyKGlzLm5hKGxhdCkgPT0gVFJVRSkpDQpgYGANCg0KV2UgY2hlY2sgb3VyIGRhdGEgdG8gc2VlIGhvdyBtYW55IE5BIHZhbHVlcyBjYW1lIGJhY2suDQoNCmBgYHtyIGhvd19tYW55X25hX2RlX3ByZSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0KDQpzdW1tYXJ5KGlzLm5hKGVycm9yc19kZV9wcmUkbGF0KSkNCg0KYGBgDQoNCiMjIDcuNC4gTWFudWFsbHkgZ2VvY29kZSByZW1haW5pbmcgb2JzZXJ2YXRpb25zDQoNClRoZSBvYnNlcnZhdGlvbnMgdGhhdCBkb24ndCBnZW9jb2RlIHdpbGwgbmVlZCB0byBiZSBkZWFsdCB3aXRoIG1hbnVhbGx5IChlLmcuIGlucHV0IGxhdC9sb24gY29vcmRpbmF0ZXMgYmFzZWQgb24gZ29vZ2xlIG1hcCByZXNlYXJjaCkuIFNpbWlsYXJseSwgb2JzZXJ2YXRpb25zIHRoYXQgZ2VvY29kZSBwb29ybHkgYW5kIGVuZCB1cCBvbiBvdXIgYGVycm9yc19kZV9wcmVgIGxpc3QgbmVlZCB0byBiZSBtYW51YWxseSBnZW9jb2RlZC4NCg0KVGhpcyBoYXMgdG8gYmUgZG9uZSBieSBoYW5kIHVuZm9ydHVhdGVseS4gWW91IGNhbiBnbyB0aHJvdWdoIHRoZSBvYnNlcnZhdGlvbnMgd2hpY2ggZWl0aGVyIGhhdmUgTkEgdmFsdWVzIGZvciBsYXQvbG9uIGluIGBkZV9nZW9jb2RlZF9wcmVgIG9yIGFyZSBmb3VuZCB0byBiZSBvdXRzaWRlIERFIGluIHRoZSBgZXJyb3JzX2RlX3ByZWAgZGF0YWZyYW1lLiBIZXJlIHdlIGRvIHRoaXMgd2l0aCB3ZWIgcmVzZWFyY2ggYW5kIGBtdXRhdGVgIHN0YXRlbWVudHMgd2hlcmUgd2UgcmVwbGFjZSB0aGUgbGF0L2xvbiB2YWx1ZXMgZm9yIGluZGl2aWR1YWwgbGljZW5zZXMuDQoNCkluIG90aGVyIGRhdGEgc2V0cyBhbmQgbGF0ZXIgaW4gdGhpcyBjb2RlIHdlIGV4cG9ydCB0aGUgZGF0YSB0byBhIGNzdiBhbmQgYW4gUkEgZG9lcyB0aGUgY29kaW5nLCB3aGljaCB3ZSB0aGVuIHJlLWltcG9ydCBhbmQgaW50ZWdyYXRlLiBUaGUgZXhwb3J0IGFwcHJvYWNoIGlzIHByb2JhYmx5IGxlc3MgcHJvbmUgdG8gZXJyb3IgZnJvbSB0cmFuc2NyaXB0aW9uIHByb2JsZW1zLg0KDQpUaGVyZSBhcmUgQUJTT0xVVEVMWSBnb2luZyB0byBiZSBsaWNlbnNlcyBmcm9tIG91dHNpZGUgRGVsYXdhcmUgb24gdGhpcyBsaXN0LiBZb3UgaGF2ZSB0byBjaGVjayB0aGUgemlwIGNvZGVzLg0KDQpOb3RlIHRoYXQgaW4gb3VyIGxhc3Qgc3RlcCwgd2UgcmVtb3ZlIGFueSBvYnNydmF0aW9ucyB0aGF0IGFyZSBvdXRzaWRlIHRoZSBzdGF0ZSB3aXRoIGEgYGZpbHRlcmAgY29tbWFuZC4NCg0KYGBge3IgZml4X21pc3NpbmdfaW5mb19kZV9wcmUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT0gRkFMU0UsY2FjaGU9VFJVRSwgZXZhbCA9IEZBTFNFfQ0KZGVfZ2VvY29kZWRfcHJlX2ZpeGVkIDwtIGRlX2dlb2NvZGVkX3ByZSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09IDIwMDQyMDA0NTksIDM5LjU2Mzc4OSwgIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAyMDA0MjAwNDU5LCAtNzUuNjUzMzk3LCBsb24pKSAgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAyMDAwMTA3ODQyLCAzOC45MjUxMDgsICAgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09IDIwMDAxMDc4NDIsIC03NS41NjcwNTcsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gMjAxNjYwNDU3NSwgMzkuNjQ5NjAwLCAgIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAyMDE2NjA0NTc1LCAtNzUuNjIxODQzICwgbG9uKSkgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAyMDA3MjIwMDM5LCAzOS43MzkwNzQsICAgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09IDIwMDcyMjAwMzksIC03NS42MjMyOTMgLCBsb24pKSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09IDE5ODkwMDUxNTgsIDM5LjgyMzg1OSwgICBsYXQpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gMTk4OTAwNTE1OCwgLTc1LjQ5OTkyNiAsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gMjAxOTYwMzEwOCwgMzguNDU1OTcyLCAgIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAyMDE5NjAzMTA4LCAtNzUuNTU5NzIyICwgbG9uKSkgJT4lDQogIGZpbHRlcighYWNjb3VudCAlaW4lIGMoMjAxNjYwNTU2NSwgMjAxMjEwMTIxOCwgMjAxMDEwMjI2MCwgMTk5ODIwNDc2MiwgMjAxOTEwMDI3MywNCiAgICAgICAgICAgICAgICAgICAgICAgICAxOTg5MDIzMTEzLCAyMDIwNzAxNjQyLCAyMDIwNzAxNjQ0LCAyMDE4NjAyNDI4LCAyMDE4NjAwNzMwLA0KICAgICAgICAgICAgICAgICAgICAgICAgIDIwMTYxMDAxNTAsIDE5OTYxMDUxNDkpKQ0KDQpgYGANCg0KIyMgNy41LiBXcml0ZSBvdXQgdGhlIGRhdGFzZXQNCg0KV2UgZXhwb3J0IG91ciBERSBiYXNlbGluZSBkYXRhIHNldCBhcyBhIGNzdi4NCg0KYGBge3IgZXhwb3J0X2RlX3ByZSwgd2FybmluZz0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBUUlVFLCBldmFsID0gRkFMU0V9DQp3cml0ZS5jc3YoZGVfZ2VvY29kZWRfcHJlX2ZpeGVkLCAiLy9qb3ZlLmRlc2lnbi51cGVubi5lZHUvRGVwdC1TaGFyZXMvcHJheC8wMSBQcm9qZWN0IEZvbGRlcnMvMjAxOV9Bbm5lbmJlcmdfR2VvU2Nhbm5pbmcvZGF0YU91dHB1dHMvZGVfZ2VvY29kZWRfcHJlXzZfOF8yMC5jc3YiKQ0KYGBgDQoNCg0KIyA4LiBERSBEYXRhIC0gU29jcmF0YSBSb3V0aW5lIGZvciBOZXcgRGF0YQ0KDQoqVGhpcyBpcyB0aGUgZGF0YSByb3V0aW5lIHdlIHdpbGwgdXNlIGZvciB1cGRhdGluZyB0aGUgREUgZGF0YS4gSXRzIG5hbWluZyBjb252ZW50aW9ucyBjb25mb3JtIHRvIHNpbWlsYXIgcm91dGluZXMgZm9yIE5KIGFuZCBQQSBhbmQgaXRzIGRhdGEgb3V0cHV0cyB3aWxsIGludGVncmF0ZSB3aXRoIHRob3NlIG90aGVyIHN0YXRlcy4qIA0KDQpUaGlzIHJvdXRpbmUgaW52b2x2ZXMgZG93bmxvYWRpbmcgbGljZW5zZSBkYXRhIGZyb20gdGhlIERFIFNPREEgQVBJIGVuZHBvaW50LCAoc28gbG9uZyBhcyB0aGF0IGlzIHRoZSBzdGFuZGFyZCBmb3JtYXQgZm9yIHN1Y2ggZGF0YSkgYW5kIGNyb3NzLWNoZWNraW5nIGl0IGFnYWluc3QgYmFzZWxpbmUgZGF0YSB0byBjbG9zZSBvdXQgb2xkIHJldGFpbGVyIGxvY2F0aW9ucyBhbmQgYWRkIGVudHJpZXMgZm9yIG5ldyBvbmVzLiBPYnNlcnZhdGlvbnMgd2l0aG91dCBhIGdlb2xvY2F0aW9uIGluIHRoZSBzdGF0ZSBkYXRhYmFzZSBhcmUgdGhlbiBnZW9jb2RlZCBhbmQgZXJyb3JzIGFyZSBjaGVja2VkLg0KDQojIyA4LjEuIExvYWQgbmV3IGRhdGEgZnJvbSBTb2NyYXRhDQoNClRoaXMgcm91dGluZSBpcyBiYXNpY2FsbHkgdGhlIHNhbWUgYXMgdGhlIGJhc2VsaW5lIHJvdXRpbmUgZnJvbSBQQSwgd2hpY2ggdXNlcyB0aGUgc2FtZSBnZW5lcmFsIGRhdGEgc3RhbmRhcmRzLCBidXQgaXQgYXBwbGllcyB0byBhIGJyb2FkIHNldCBvZiBidXNpbmVzcyBsaWNlbnNlcyB0aGF0IGFyZSBwdWJsaXNoZWQgYXMgYSBncm91cC4NCg0KU3RlcHM6DQoNCjEuIEdyYWIgdGhlIGRhdGEgZnJvbSB0aGUgREUgU09EQSBBUEkuIChgcmVhZC5zb2NyYXRhYCkNCg0KMi4gRmlsdGVyIHRvIGtlZXAgb25seSBkYXRhIG9mIHRoZSBjYXRlZ29yaWVzIGBUT0JBQ0NPIFBST0RVQ1RTIFJFVEFJTEVSYCBhbmQgYFJFVEFJTEVSLVRPQkFDQ09gDQoNCjMuIEFkZCBhIHRpbWUgc3RhbXAgd2l0aCB0aGUgZGF0ZSBvZiBkb3dubG9hZCwgYSBjb2x1bW4gZm9yICdzdGF0ZScgYW5kIGNvZXJjZSB0aGUgbGljZW5zZSBleHBpcmF0aW9uIGRhdGUgdG8gZGF0ZSAoJ3ltZCcpDQoNCjQuIFJlbmFtZSByZWxldmFudCBjb2x1bW5zIHRvIGxhdCwgbG9uLCBhY2NvdW50IGFuZCBsaWNlbnNlX3R5cGUNCg0KNS4gRmlsdGVyIGp1c3QgZm9yIG9ic2VydmF0aW9ucyB3aXRoIHppcCBjb2RlcyBiZXR3ZWVuIDE5NzAwMSBhbmQgMTk5ODAgKERlbGF3YXJlIFppcCBjb2RlcykgLSB0aGlzIGF2b2lkcyBwcm9ibGVtcyB3ZSBoYWQgY3JlYXRpbmcgYmFzZWxpbmUgZGF0YSB3aXRoIG9kZCBvdXQtb2Ytc3RhdGUgcG9pbnRzDQoNCjYuIGB1bml0ZWAgYWRkcmVzcyBmaWVsZHMgaW50byBvbmUgYWRkcmVzcyBjb2x1bW4gZm9yIGdlb2NvZGluZw0KDQo3LiBSZW1vdmUgdW4tbmVlZGVkIHZhcmlhYmxlcyB1c2luZyBgc2VsZWN0YA0KDQo4LiBVc2UgYG11dGF0ZV9pZmAgdG8gY29lcmNlIGFsbCBmYWN0b3JzIHRvIGNoYXJhY3Rlcg0KDQo5LiBSZXBsYWNlICImIiBjaGFyYWN0ZXJzIHdpdGggIkFORCIgKGJldHRlciBmb3IgZ2VvY29kaW5nKQ0KDQoNCmBgYHtyIHJlYWRfU29jcmF0YV9kZSwgY2FjaGUgPSBUUlVFLCBldmFsID0gRkFMU0V9DQpyZXRhaWxlcnNfU29jcmF0YV9kZSA8LSByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5kZWxhd2FyZS5nb3YvcmVzb3VyY2UvNXp5Mi1ncmhyLmpzb24iKSAlPiUNCiAgZmlsdGVyKHN0cl9kZXRlY3QoY2F0ZWdvcnksICJUT0JBQ0NPIFBST0RVQ1RTIFJFVEFJTEVSIikgfCBzdHJfZGV0ZWN0KGNhdGVnb3J5LCAiUkVUQUlMRVItVE9CQUNDTyIpKSAlPiUNCiAgbXV0YXRlKHB1Ymxpc2hfZGF0ZSA9IHltZChjdXJyZW50X2xpY2Vuc2VfdmFsaWRfZnJvbSksDQogICAgICAgICBleHBpcmF0aW9uX2RhdGUgPSB5bWQoY3VycmVudF9saWNlbnNlX3ZhbGlkX3RvKSwNCiAgICAgICAgIGV4cGlyZWRfeV9uID0gIkFDVElWRSIsDQogICAgICAgICB0cmFkZV9uYW1lID0gYnVzaW5lc3NfbmFtZSwNCiAgICAgICAgIGNvdW50eSA9IE5BLA0KICAgICAgICAgc3RhdGUgPSAiREUiKSAlPiUNCiAgcmVuYW1lKGxhdCA9IGdlb2NvZGVkX2xvY2F0aW9uLmxhdGl0dWRlLA0KICAgICAgICAgbG9uID0gZ2VvY29kZWRfbG9jYXRpb24ubG9uZ2l0dWRlLA0KICAgICAgICAgYWNjb3VudCA9IGxpY2Vuc2VfbnVtYmVyLA0KICAgICAgICAgbGljZW5zZV90eXBlID0gY2F0ZWdvcnkpICU+JQ0KICBtdXRhdGUoemlwID0gaWZlbHNlKHN0cl9sZW5ndGgoemlwKSA9PSA5LCBzdHJfc3ViKHppcCwgZW5kID0gLTUpLCB6aXApKSAlPiUNCiAgZmlsdGVyKHppcCA+PSAxOTcwMSAmIHppcCA8PSAxOTk4MCkgJT4lDQogIHVuaXRlKC4sICJhZGRyZXNzX2Z1bGwiLCBzZXAgPSAiICIsDQogICAgICAgIGMoImFkZHJlc3NfMiIsICJjaXR5IiwgInN0YXRlIiwgInppcCIpKSAlPiUNCiAgbXV0YXRlKHN0YXRlID0gIkRFIikgJT4lDQogIGRwbHlyOjpzZWxlY3QoLWJ1c2luZXNzX25hbWUsIC1jdXJyZW50X2xpY2Vuc2VfdmFsaWRfZnJvbSwgDQogICAgICAgICAgICAgICAgLWN1cnJlbnRfbGljZW5zZV92YWxpZF90bywgLWNvdW50cnksIC1nZW9jb2RlZF9sb2NhdGlvbi5odW1hbl9hZGRyZXNzLA0KICAgICAgICAgICAgICAgIC1hZGRyZXNzXzEpICU+JQ0KICBtdXRhdGVfaWYoaXMuZmFjdG9yLCBhcy5jaGFyYWN0ZXIpICU+JQ0KICBtdXRhdGUoYWRkcmVzc19mdWxsID0gc3RyX3JlcGxhY2UoYWRkcmVzc19mdWxsLCAiJiIsICJBTkQiKSkNCmBgYA0KDQojIyA4LjIuIExvYWQgbW9zdCBjdXJyZW50IHN0b3JlZCBkYXRhIHNldA0KDQpXZSBsb2FkIHVwIG91ciBzYXZlZCBiYXNlbGluZSBkYXRhLCBmcm9tIHdoZXJldmVyIHRoYXQgbWF5IGJlIHN0b3JlZC4gKldoZW4gcnVubmluZyB0aGlzIGNvZGUsIHRoZSBmaWxlcGF0aCBpbiB0aGUgZm9sbG93aW5nIGNvZGUgY2h1bmsgd2lsbCBsaWtlbHkgbmVlZCB0byBiZSBjaGFuZ2VkIHRvIHJlZmxlY3QgdGhlIG1vc3QgcmVjZW50IERFIGJhc2VsaW5lIGRhdGEgc2V0Ki4NCg0KTm90ZSB0aGF0IHdlIGBkcGx5cjo6c2VsZWN0YCBvbmx5IG91ciBgY2Fub25pY2FsX25hbWVzYCB0byBhY2NvdW50IGZvciB0aGUgZmFjdCB0aGF0IHdoZW4gd2Ugd3JvdGUgdGhlc2UgZGF0YSBvdXQsIHRoZXkgbGlrZWx5IHdlcmUgaW1wYXJ0ZWQgd2l0aCBzb21lIGtpbmQgb2YgY29sdW1uIGRlbm90aW5nIGEgcm93IGlkLiBXZSBkb24ndCB3YW50IHRoYXQuDQoNCldlIGFsc28gbWFrZSBzdXJlIG91ciBkYXRhIHR5cGVzIGFyZSBjb3JyZWN0IC0gdXNpbmcgYSBgbXV0YXRlX2lmYCBzdGF0ZW1lbnQgdG8gdHVybiBmYWN0b3JzIHRvIGNoYXJhY3RlcnMgYW5kIGEgYG11dGF0ZWAgc3RhdGVtZW50IHRvIHR1cm4gb3VyIGRhdGVzIGludG8gYGRhdGVgIGZvcm1hdA0KYGBge3IgbG9hZF9kZV9zYXZlZCwgY2FjaGUgPSBUUlVFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgZXZhbCA9IEZBTFNFfQ0Kc3RvcmVkX2RlIDwtIHJlYWQuY3N2KCIvL2pvdmUuZGVzaWduLnVwZW5uLmVkdS9EZXB0LVNoYXJlcy9wcmF4LzAxIFByb2plY3QgRm9sZGVycy8yMDE5X0FubmVuYmVyZ19HZW9TY2FubmluZy9kYXRhT3V0cHV0cy9kZV9nZW9jb2RlZF9wcmVfNl84XzIwLmNzdiIpICU+JQ0KICBkcGx5cjo6c2VsZWN0KGNhbm9uaWNhbF9uYW1lcykgJT4lDQogIG11dGF0ZV9pZihpcy5mYWN0b3IsIGFzLmNoYXJhY3RlcikgJT4lDQogIG11dGF0ZShhY2NvdW50ID0gYXMuY2hhcmFjdGVyKGFjY291bnQpKSAlPiUNCiAgbXV0YXRlKGV4cGlyYXRpb25fZGF0ZSA9IHltZChleHBpcmF0aW9uX2RhdGUpLA0KICAgICAgICAgcHVibGlzaF9kYXRlID0geW1kKHB1Ymxpc2hfZGF0ZSkpDQoNCmBgYA0KDQojIyA4LjMuIEpvaW4gbmV3IGRhdGEgYW5kIHN0b3JlZCBkYXRhDQoNCkNoZWNrIHRvIHNlZSB3aGljaCBsaWNlbnNlcyBhcmUgbmV3IGluIHRoZSBpbmNvbWluZyBkYXRhLiBXaGVuIHdlIGZpbmQgbmV3IGVudHJpZXMgdGhhdCBtYXRjaCBlbnRyaWVzIGluIG91ciBleGlzdGluZyBkYXRhLCB3ZSB1cGRhdGUgdGhlaXIgZGF0ZSBvZiBleHBpcnkuIElmIGRhdGEgZW50cmllcyBpbiB0aGUgb2xkIGRhdGFiYXNlIGFyZSBub3QgcmVwcmVzZW50ZWQgaW4gdGhlIG5ldyBvbmUsIHdlIG1hcmsgdGhlbSBhcyBleHBpcmVkLg0KDQpOb3RlIHRoZSB1c2Ugb2YgdGhlIGBkcGx5cmAgdmVyc2lvbiBvZiBgaWZlbHNlYCBjYWxsZWQgYGlmX2Vsc2VgIGhlcmUgaW4gb3JkZXIgdG8gbWFrZSBzdXJlIHRoZSByZXN1bHQgaXMgYSBkYXRlIGFuZCBub3QgbnVtZXJpYy4NCg0KSGVyZSdzIHdoYXQgdGhpcyBkb2VzOg0KDQoxLiAgRnVsbCBKb2luIG9mIHJldGFpbGVycyBmcm9tIGJhc2VsaW5lIERFIGRhdGEgKGBzdG9yZWRfZGVgKSB0byBuZXcgZGF0YSAoYHJldGFpbGVyc19Tb2NyYXRhX2RlYCksIGpvaW5lZCBieSBib3RoIGFjY291bnQgYW5kIHRyYWRlIG5hbWUuIFNpbmNlIHRoZSBkYXRhIHNldHMgaGF2ZSBpZGVudGljYWwgbmFtZXMsIHRoaXMgcHJvZHVjZXMgYW4gIngiIGFuZCAieSIgY29sdW1uIGZvciBlYWNoIG9mIG91ciBjYW5vbmljYWwgbmFtZXMgLSB4IGJlaW5nIHRoZSBsZWZ0IHNpZGUgYW5kIHkgYmVpbmcgdGhlIHJpZ2h0IHNpZGUgb2YgdGhlIGpvaW4uDQoNCjIuICBJZiB0aGVyZSBpcyBubyBleHBpcmF0aW9uIGRhdGUgb24gZmlsZSBmb3IgdGhlIG5ldyBzZXQgKGUuZy4gdGhlcmUgaXMgbm8gZW50cnkgZm9yIGEgcmV0YWlsZXIgb24gZmlsZSBpbiB0aGUgbmV3ZXN0IGRhdGEgc2V0KSwgYXNzdW1lIHRoZSBhY2NvdW50IGlzIGV4cGlyZWQgLSBtYXJrIGl0ICJFWFBJUkVEIiBvciBlbHNlICJBQ1RJVkUiDQoNCjMuIElmIGl0J3MgZXhwaXJlZCAoZS5nLiB0aGVyZSBpcyBubyBjb3JyZXNwb25kaW5nIHkgZW50cnkgZm9yIGEgZ2l2ZW4geCBlbnRyeSksIG1hcmsgYW4gZXhwaXJhdGlvbiBkYXRlIGFzIGJlaW5nIHRoZSBsYXN0IGFjdGl2ZSBkYXRlIG9mIHRoZSBsaWNlbnNlIG9uIGZpbGUgZnJvbSB0aGUgZXhpc3RpbmcgZGF0YWJhc2UuDQoNCjQuIENyZWF0ZSBhIG5ldyBzZXQgb2YgY2Fub25pY2FsIGNvbHVtbnMgYnkgY2hlY2tpbmcgdG8gc2VlIGlmIGJvdGggeCBhbmQgeSBjb2x1bW5zIGFyZSBmaWxsZWQgZm9yIGVhY2ggb2Ygb3VyIHZhcmlhYmxlcyAtIGlmIHRoZXJlJ3Mgbm8geCwgcHV0IHRoZSB5IGRhdGEgaW4gdGhlcmUgYW5kIHZpY2UgdmVyc2EuDQoNCjUuIFVzZSBzaW1pbGFyIGxvZ2ljIHRvIGZpbGwgaW4gYWRkcmVzcywgY291bnR5LCBwdWJsaXNoIGRhdGUgYW5kIGxpY2Vuc2UgdHlwZSBkYXRhLg0KDQo2LiBSZW1vdmUgdGhlIGNvbHVtbnMgd2hpY2ggYXJlIGxlZnQgb3ZlciBmcm9tIHRoZSBqb2luIChlLmcuIGBsYXQueGAsIGBsaWNlbnNlX3R5cGUueWApIHVzaW5nIGBkcGx5cjo6c2VsZWN0YA0KDQpJbiB0aGlzIGpvaW4gKnRoZSBtb3N0IHJlY2VudCBkYXRhIHNldCBnb2VzIHNlY29uZCAoZS5nLiB0aGUgInkiKSBkYXRhIHNldC4qDQoNCmBgYHtyIGpvaW5lZF9kZSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPSBGQUxTRSxjYWNoZT1UUlVFLCBldmFsID0gRkFMU0V9DQpqb2luZWRfZGUgPC0gZnVsbF9qb2luKHN0b3JlZF9kZSwgDQogICAgICAgICAgICAgICAgICAgICByZXRhaWxlcnNfU29jcmF0YV9kZSwgYnkgPSBjKCJhY2NvdW50IiwgInRyYWRlX25hbWUiKSkgJT4lDQogIG11dGF0ZShleHBpcmVkX3lfbiA9IGlmZWxzZShpcy5uYShleHBpcmF0aW9uX2RhdGUueSkgPT0gVFJVRSwgIkVYUElSRUQiLCAiQUNUSVZFIiksDQogICAgICAgICBleHBpcmF0aW9uX2RhdGUgPSBpZl9lbHNlKGV4cGlyZWRfeV9uID09ICdFWFBJUkVEJywgZXhwaXJhdGlvbl9kYXRlLngsIHltZChleHBpcmF0aW9uX2RhdGUueSkpLA0KICAgICAgICAgbGF0ID0gaWZlbHNlKGlzLm5hKGxhdC54KSA9PSBGQUxTRSwgbGF0LngsIGxhdC55KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShpcy5uYShsb24ueCkgPT0gRkFMU0UsIGxvbi54LCBsb24ueSksDQogICAgICAgICBhZGRyZXNzX2Z1bGwgPSBpZmVsc2UoaXMubmEoYWRkcmVzc19mdWxsLngpID09IEZBTFNFLCBhZGRyZXNzX2Z1bGwueCwgYWRkcmVzc19mdWxsLnkpLA0KICAgICAgICAgY291bnR5ID0gaWZlbHNlKGlzLm5hKGNvdW50eS55KSA9PSBUUlVFLCBjb3VudHkueCwgY291bnR5LnkpLA0KICAgICAgICAgcHVibGlzaF9kYXRlID0gaWZfZWxzZShpcy5uYShwdWJsaXNoX2RhdGUueSkgPT0gVFJVRSwgcHVibGlzaF9kYXRlLngsIHB1Ymxpc2hfZGF0ZS55KSwNCiAgICAgICAgIGxpY2Vuc2VfdHlwZSA9IGlmZWxzZShpcy5uYShsaWNlbnNlX3R5cGUueSkgPT0gVFJVRSwgbGljZW5zZV90eXBlLngsIGxpY2Vuc2VfdHlwZS55KSwNCiAgICAgICAgIHN0YXRlID0gIkRFIikgJT4lDQogIGRwbHlyOjpzZWxlY3QoLWxhdC54LCAtbG9uLngsIC1sYXQueSwgLWxvbi55LCANCiAgICAgICAgIC1hZGRyZXNzX2Z1bGwueCwgLWFkZHJlc3NfZnVsbC55LCAtY291bnR5LngsIC1jb3VudHkueSwgLWV4cGlyZWRfeV9uLngsIC1leHBpcmVkX3lfbi55LA0KICAgICAgICAgLXB1Ymxpc2hfZGF0ZS54LCAtcHVibGlzaF9kYXRlLnksIC1zdGF0ZS54LCAtc3RhdGUueSwgLWxpY2Vuc2VfdHlwZS54LCAtbGljZW5zZV90eXBlLnksDQogICAgICAgICAtZXhwaXJhdGlvbl9kYXRlLngsIC1leHBpcmF0aW9uX2RhdGUueSkNCmBgYA0KDQpBIHN1bW1hcnkgY2FsbCBkZXRlcm1pbmVzIGlmIHRoZXJlIGFyZSBhbnkgYmFkIGdlb2NvZGVzLg0KDQpgYGB7ciByZXRhaWxlcl9uYV9zdW1tYXJ5X2pvaW5fZGUyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9IEZBTFNFLGNhY2hlPVRSVUUsIGV2YWwgPSBGQUxTRX0NCnN1bW1hcnkoaXMubmEoam9pbmVkX2RlJGxhdCkpDQpgYGANCg0KSWYgdGhlcmUgYXJlIE5PIFRSVUUgdmFsdWVzIHRvIHRoaXMgc3RhdGVtZW50LCB0aGUgZm9sbG93aW5nIHN0ZXAgY2FuIGJlIHNraXBwZWQgKGFsdGhvdWdoIHlvdSB3aWxsIG5lZWQgdG8gcmVuYW1lIGBqb2luZWRfZGVgIGFzIGBnZW9jb2RlZF9kZWAgdG8gZ2V0IHN0ZXAgOC41IHRvIHdvcmsgY29ycmVjdGx5KS4NCg0KIyMgOC40LiBHZW9jb2RlIG1pc3NpbmcgaW5mb3JtYXRpb24NCg0KTm93IHdlIGdlb2NvZGUgdGhlIG9ic2VydmF0aW9ucyB3aGljaCBsYWNrIGEgbGF0L2xvbiAoYHRvX2dlb2NvZGVfZGVgKSBieSBzZW5kaW5nIGFkZHJlc3NlcyB0byB0aGUgR29vZ2xlIGdlb2NvZGluZyBlbmdpbmUsIGZldGNoaW5nIGEgbGF0L2xvbiBwYWlyIChgZ2VvY29kZWApIGFuZCB0aGVuIGJpbmRpbmcgdGhlIGxhdC9sb24gY29sdW1ucyBiYWNrIHRvIHRoZSByZXN0IG9mIHRoZSBkYXRhIChgY2JpbmRgKSBhbmQgYmluZGluZyB0aGUgZ2VvY29kZWQgb2JzZXJ2YXRpb25zIGJhY2sgdG8gdGhvc2UgZGF0YSB3aGljaCBoYWQgYSBsYXQvbG9uIHZhbHVlIChgcmJpbmRgKS4NCg0KVGhlIGBzdW1tYXJ5YCBmdW5jdGlvbiB3aWxsIHRlbGwgdXMgaG93IG1hbnkgZ2VvY29kZXMgaGF2ZSBiZWVuIHVuc3VjY2Vzc2Z1bCB1cCB1bnRpbCB0aGlzIHBvaW50Lg0KDQpgYGB7ciByZXRhaWxlcl9uYV9zdW1tYXJ5X2pvaW5fZGUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT0gRkFMU0UsY2FjaGU9VFJVRSwgZXZhbCA9IEZBTFNFfQ0KdG9fZ2VvY29kZV9kZSA8LSBqb2luZWRfZGUgJT4lDQogIGZpbHRlcihpcy5uYShsYXQpID09IFRSVUUpDQoNCmdlb2NvZGVkX2RlIDwtIGdlb2NvZGUodG9fZ2VvY29kZV9kZSRhZGRyZXNzX2Z1bGwsIHNvdXJjZSA9ICAiZ29vZ2xlIikgJT4lDQogIGNiaW5kKC4sIHRvX2dlb2NvZGVfZGUgJT4lDQogICAgICAgICAgc2VsZWN0KC1sYXQsIC1sb24pKSAlPiUNCiAgcmJpbmQoam9pbmVkX2RlICU+JQ0KICAgICAgICAgIGZpbHRlcihpcy5uYShsYXQpID09IEZBTFNFKSwgLikNCg0Kc3VtbWFyeShpcy5uYShnZW9jb2RlZF9kZSRsYXQpKQ0KYGBgDQoNCklmIHRoaXMgc3RlcCBnaXZlcyB5b3UgYW4gZXJyb3IgcmVhZGluZyBgRXJyb3IgaW4gaWYgKGxvY2F0aW9uID09ICIiKSByZXR1cm4ocmV0dXJuX2ZhaWxlZF9nZW9jb2RlKG91dHB1dCkpIDogYXJndW1lbnQgaXMgb2YgbGVuZ3RoIHplcm9gLCBydW4gdGhlIGZvbGxvd2luZyBjb2RlOg0KDQpgYGB7ciByZW5hbWVfaWZfZXJyb3JfZGUsIGV2YWw9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPSBUUlVFLCBldmFsID0gRkFMU0V9DQpnZW9jb2RlZF9kZSA8LSBqb2luZWRfZGUNCmBgYA0KDQoNCiMjIDguNS4gUGVyZm9ybSBxdWFsaXR5IGNoZWNrIGZvciBiYWQgZ2VvY29kZXMNCg0KV2hlbiBnZW9jb2RlcyBhcmUgd3JvbmcsIHRoZXkgYXJlIG9mdGVuIHZlcnkgd3JvbmcsIGFuZCBzZXQgc29tZXdoZXJlIHdpbGRseSBkaWZmZXJlbnQgZnJvbSBvdXIgdGFyZ2V0IGFyZWEuIFdlIGRvIGEgc3BhdGlhbCBqb2luIHdpdGggdGhlIERFIHNoYXBlZmlsZSwga2VlcCBvbmx5IHRoZSBvYnNlcnZhdGlvbnMgd2hpY2ggZmFsbCBvdXRzaWRlIG9mIERFLiBXZSBjYW4gY2hlY2sgdGhlc2Ugb2JzZXJ2YXRpb25zIGFuZCBwb3RlbnRpYWxseSBnZW9jb2RlIHRoZW0gbWFudWFsbHkuDQoNCldlIGRvIHRoaXMgdGhyb3VnaCB0aGUgZm9sbG93aW5nIHN0ZXBzOg0KDQoxLiBQZXJmb3JtIGEgc3BhdGlhbCBqb2luIHVzaW5nIGBzdF9qb2luYCBmcm9tIHRoZSBgc2ZgIHBhY2thZ2UgdG8gc2VlIHdoaWNoIHBvaW50cyBmcm9tIGBnZW9jb2RlZF9kZWAgZmFsbCBvdXRzaWRlIERFIChleGNsdWRpbmcgdGhvc2Ugd2Uga25vdyBhcmUgTkEgYXQgcHJlc2VudCkuIFRvIGRvIHRoaXMgd2UgbmVlZCB0byByZXByb2plY3Qgb3VyIGxhdC9sb24gZGF0YSBhcyBhbiBgc2ZgIHZlY3RvciBvYmplY3QgcHJvamVjdGVkIGluIFdHUzg0ICh3ZWIgbWVyY2F0b3IsIGBjcnMgPSA0MzI2YCkgYW5kIGpvaW4gaXQgdG8gYGRlX3NocGAuDQoNCjIuIFdlIGBmaWx0ZXJgIHRob3NlIG9ic2VydmF0aW9ucyB3aGljaCBjb21lIGJhY2sgYXMgYGlzLm5hKFNUVVNQUykgPT0gVFJVRWAgKGUuZy4gdGhleSBkb24ndCBqb2luIGF0IGFsbCB0byB0aGUgREUgdGlncmlzIHNoYXBlZmlsZSBhbmQgZG9uJ3QgdGFrZSBvbiBhbnkgb2YgaXQncyBhdHRyaWJ1dGVzIGxpa2UgU1RVU1BTKSBhbmQga25vdyB0aGF0IHRoZXNlIGFyZSB0aGUgb25lcyB3aGljaCBmZWxsIG91dHNpZGUgb3VyIHNoYXBlZmlsZS4NCg0KMy4gV2UgYGRwbHlyOjpzZWxlY3RgIG9ubHkgb3VyIGNhbm9uaWNhbCB2YXJpYWJsZXMgZnJvbSB0aGVzZSBhbmQgZXh0cmFjdCB0aGUgbGF0L2xvbiBmcm9tIHRoZWlyIGdlb21ldHJpZXMgdXNpbmcgYSBgbXV0YXRlYCBzdGF0ZW1lbnQuIFdlIHdhbnQgdG8gdHVybiB0aGlzIGJhY2sgaW50byBhIGRhdGFmcmFtZSB3aXRoIGxhdC9sb24gZGF0YSBhbmQgbm90IGdlb21ldHJpZXMgbGlrZSBhbiBgc2ZgIG9iamVjdC4gU28gd2Ugc2V0IGl0IGBhcy5kYXRhLmZyYW1lYCBhbmQgcmVtb3ZlIHRoZSBgZ2VvbWV0cnlgIGNvbHVtbi4NCg0KNC4gV2Ugbm93IGByYmluZGAgdGhlc2UgZGF0YSBiYWNrIHRvIG91ciBgZ2VvY29kZWRfZGVgIG9ic2VydmF0aW9ucyB3aGljaCBsYWNrZWQgbGF0L2xvbiBpbiB0aGUgZmlyc3QgcGxhY2UsIHRvIGNyZWF0ZSBhIGRhdGEgc2V0IGNvbnNpc3Rpbmcgb2YgYWxsIG91ciBvYnNlcnZhdGlvbnMgd2hpY2ggd2UgcHJlc3VtZSBoYXZlIGJhZCBnZW9jb2Rlcy4NCg0KDQpgYGB7ciBkZXRlY3RfZXJyb3JzX2RlLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBUUlVFLCBldmFsID0gRkFMU0V9DQplcnJvcnNfZGUgPC0gc3Rfam9pbihnZW9jb2RlZF9kZSAlPiUgDQogICAgICAgICAgZmlsdGVyKGlzLm5hKGxhdCkgPT0gRkFMU0UpICU+JSANCiAgICAgICAgICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJsb24iLCAibGF0IiksIGNycyA9IDQzMjYpLCANCiAgICAgICAgZGVfc2hwLCANCiAgICAgICAgam9pbiA9IHN0X3dpdGhpbiwgDQogICAgICAgIGxlZnQgPSBUUlVFKSAlPiUNCiAgZmlsdGVyKGlzLm5hKFNUVVNQUykgPT0gVFJVRSkgJT4lDQogIG11dGF0ZShsb249bWFwX2RibChnZW9tZXRyeSwgfnN0X2NlbnRyb2lkKC54KVtbMV1dKSwNCiAgICAgICAgIGxhdD1tYXBfZGJsKGdlb21ldHJ5LCB+c3RfY2VudHJvaWQoLngpW1syXV0pKSU+JQ0KICBkcGx5cjo6c2VsZWN0KGNhbm9uaWNhbF9uYW1lcykgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtZ2VvbWV0cnkpICU+JQ0KICByYmluZCguLCBnZW9jb2RlZF9kZSAlPiUNCiAgICAgICAgICBmaWx0ZXIoaXMubmEobGF0KSA9PSBUUlVFKSkNCmBgYA0KDQojIyA4LjYuIE1hbnVhbGx5IGdlb2NvZGUgcmVtYWluaW5nIG9ic2VydmF0aW9ucw0KDQpUaGUgb2JzZXJ2YXRpb25zIHRoYXQgZG9uJ3QgZ2VvY29kZSB3aWxsIG5lZWQgdG8gYmUgZGVhbHQgd2l0aCBtYW51YWxseSAoZS5nLiBpbnB1dCBsYXQvbG9uIGNvb3JkaW5hdGVzIGJhc2VkIG9uIGdvb2dsZSBtYXAgcmVzZWFyY2gpLiBTaW1pbGFybHksIG9ic2VydmF0aW9ucyB0aGF0IGdlb2NvZGUgcG9vcmx5IGFuZCBlbmQgdXAgb24gb3VyIGBlcnJvcnNfZGVfcHJlYCBsaXN0IG5lZWQgdG8gYmUgbWFudWFsbHkgZ2VvY29kZWQuDQoNClRoaXMgaGFzIHRvIGJlIGRvbmUgYnkgaGFuZCB1bmZvcnR1YXRlbHkuIFlvdSBjYW4gZ28gdGhyb3VnaCB0aGUgb2JzZXJ2YXRpb25zIHdoaWNoIGVpdGhlciBoYXZlIE5BIHZhbHVlcyBmb3IgbGF0L2xvbiBpbiBgZGVfZ2VvY29kZWRfcHJlYCBvciBhcmUgZm91bmQgdG8gYmUgb3V0c2lkZSBERSBpbiB0aGUgYGVycm9yc19kZV9wcmVgIGRhdGFmcmFtZS4gSGVyZSB3ZSBkbyB0aGlzIHdpdGggd2ViIHJlc2VhcmNoIGFuZCBgbXV0YXRlYCBzdGF0ZW1lbnRzIHdoZXJlIHdlIHJlcGxhY2UgdGhlIGxhdC9sb24gdmFsdWVzIGZvciBpbmRpdmlkdWFsIGxpY2Vuc2VzLg0KDQpJbiBvdGhlciBkYXRhIHNldHMgYW5kIGVsc2V3aGVyZSBpbiB0aGlzIGNvZGUgd2UgZXhwb3J0IHRoZSBkYXRhIHRvIGEgY3N2IGFuZCBhbiBSQSBkb2VzIHRoZSBjb2RpbmcsIHdoaWNoIHdlIHRoZW4gcmUtaW1wb3J0IGFuZCBpbnRlZ3JhdGUuIFRoZSBleHBvcnQgYXBwcm9hY2ggaXMgcHJvYmFibHkgbGVzcyBwcm9uZSB0byBlcnJvciBmcm9tIHRyYW5zY3JpcHRpb24gcHJvYmxlbXMuDQoNCmBgYHtyIGZpeF9taXNzaW5nX2luZm9fZGUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT0gRkFMU0UsY2FjaGU9VFJVRSwgZXZhbCA9IEZBTFNFfQ0KZ2VvY29kZWRfZGVfZml4ZWQgPC0gZ2VvY29kZWRfZGUgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiMjAwMzEwMTczMyIsIDM4LjgxNjc4NCwgICBsYXQpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjIwMDMxMDE3MzMiLCAtNzUuMjk5NzIyLCBsb24pKSAlPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICIyMDIwNzAzMzQ1IiwgMzguNDU2MTg2LCAgICAgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICIyMDIwNzAzMzQ1IiwgLTc1LjU3MzgzNiwgbG9uKSkgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiMjAxMDEwNDc1NSIsIDM4LjQ1NjcwMCwgICAgIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiMjAxMDEwNDc1NSIsIC03NS41NTU4ODEsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjIwMTExMTk2OTMiLCAzOC40NTU4MzAsICAgIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiMjAxMTExOTY5MyIsIC03NS41NTk2NjYsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjIwMDAxMDE2MjUiLCAzOC40NTgyMDcsICAgIGxhdCksDQogICAgICAgICBsb24gPSBpZmVsc2UoYWNjb3VudCA9PSAiMjAwMDEwMTYyNSIsIC03NS41NTk3NDYsIGxvbikpICU+JQ0KICBtdXRhdGUobGF0ID0gaWZlbHNlKGFjY291bnQgPT0gIjE5ODkwMzMwNTciLCAzOS4wNzA5NTEsICAgICBsYXQpLA0KICAgICAgICAgbG9uID0gaWZlbHNlKGFjY291bnQgPT0gIjE5ODkwMzMwNTciLCAtNzUuNTU0MDkzLCBsb24pKSAgJT4lDQogIG11dGF0ZShsYXQgPSBpZmVsc2UoYWNjb3VudCA9PSAiMjAxOTEwMTI0NiIsIDM5LjIxODY0MCwgICAgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICIyMDE5MTAxMjQ2IiwgLTc1LjU3MTEzOCwgbG9uKSklPiUNCiAgbXV0YXRlKGxhdCA9IGlmZWxzZShhY2NvdW50ID09ICIyMDEzMTAzMDQxIiwgMzguNTA5NTQ3LCAgICAgbGF0KSwNCiAgICAgICAgIGxvbiA9IGlmZWxzZShhY2NvdW50ID09ICIyMDEzMTAzMDQxIiwgLTc1LjIzNzkxOCwgbG9uKSkgICAgICAgDQoNCmBgYA0KDQojIyA4LjcuIFdyaXRlIG91dCB0aGUgZGF0YXNldA0KDQpUaGlzIGlzIG91ciBmaW5hbCBERSBkYXRhIHNldCAtIGBnZW9jb2RlZF9kZV9maXhlZGAuIEl0IGNhbiBiZSB3cml0dGVuIG91dCB0byBmaWxlLiBXZSB3aWxsIGJpbmQgaXQgdG8gb3VyIE5KIGFuZCBQQSBkYXRhIGxhdGVyIGluIHRoZSBjb2RlIGJhc2UuDQoNCipJZiB5b3UgYXJlIHJ1bm5pbmcgdGhpcyBjb2RlIGJhc2UgdG8gZG8gYW4gdXBkYXRlLCBjaGFuZ2UgdGhlIGZpbGVwYXRoIGJlbG93Kg0KDQpgYGB7ciBleHBvcnRfZGUsIHdhcm5pbmc9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0Kd3JpdGUuY3N2KGdlb2NvZGVkX2RlX2ZpeGVkLCAiLy9qb3ZlLmRlc2lnbi51cGVubi5lZHUvRGVwdC1TaGFyZXMvcHJheC8wMSBQcm9qZWN0IEZvbGRlcnMvMjAxOV9Bbm5lbmJlcmdfR2VvU2Nhbm5pbmcvZGF0YU91dHB1dHMvZGVfZ2VvY29kZWRfMTBfMTNfMjAuY3N2IikNCmBgYA0KDQojIDkuIENvYWxlc2NpbmcgdGhlIG1hc3RlciBkYXRhIHNldA0KDQpJZiB3ZSBhcmUgcnVubmluZyBhIHJvdXRpbmUgZm9yIGFsbCBvZiB0aGUgc3RhdGVzIGF0IG9uY2UsIHdlIGNhbiBjb2FsZXNjZSB0aGUgdGhyZWUgc3RhdGVzIGFuZCB3cml0ZSBvdXQgdGhlIGRhdGEgc2V0Lg0KDQpgYGB7ciBjb2FsZXNjZSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0KDQphbGxTdGF0ZXMgPC0gcmJpbmQoZ2VvY29kZWRfZGVfZml4ZWQsIGdlb2NvZGVkX25qX2ZpeGVkKSAlPiUNCiAgcmJpbmQoLiwgZ2VvY29kZWRfcGFfZml4ZWQpDQoNCmBgYA0KDQpgYGB7ciBvdXRwdXRfYWxsU3RhdGVzLCBldmFsID0gRkFMU0V9DQp3cml0ZS5jc3YoYWxsU3RhdGVzLCAiLy9qb3ZlLmRlc2lnbi51cGVubi5lZHUvRGVwdC1TaGFyZXMvcHJheC8wMSBQcm9qZWN0IEZvbGRlcnMvMjAxOV9Bbm5lbmJlcmdfR2VvU2Nhbm5pbmcvZGF0YU91dHB1dHMvYWxsX1JldGFpbGVyc18xMF8yMF8yMC5jc3YiKQ0KYGBgDQoNCiMjIDkuMSBNZXRob2RvbG9neSBmb3IgVXBkYXRpbmcgT25lIFN0YXRlIGF0IGEgVGltZQ0KDQpJdCdzIGxpa2VseSB0aGF0IG9uZSB3b3VsZCBqdXN0IHJ1biBhIHJvdXRpbmUgZm9yIGEgc2luZ2xlIHN0YXRlIGFuZCB0aGVuIHVwZGF0ZSBhIHBvcnRpb24gb2YgdGhlIGRhdGEgc2V0Lg0KDQpUaGUgbWV0aG9kb2xvZ3kgZm9yIGRvaW5nIHRoaXMgd291bGQgYmUgYXMgZm9sbG93czoNCg0KMS4gUnVuIHRoZSByb3V0aW5lIGZvciB0aGUgc3RhdGUgb2YgaW50ZXJlc3QuIChUaGUgYmVsb3cgZXhhbXBsZSB1c2VzIE5KKQ0KDQoyLiBVcGxvYWQgdGhlIGBhbGxTdGF0ZXNgIGRhdGEgc2V0IChlLmcuIHRoZSBtb3N0IGN1cnJlbnQsIG11bHRpLXN0YXRlIHNldCkgYW5kIGFwcGx5IHRoZSBjbGVhbmluZyBjb252ZW50aW9uIHNvIHRoYXQgdGhlIGRhdGEgdHlwZXMgYXJlIGNvcnJlY3QuDQoNCjMuIEFkZCB0aGUgbmV3IG9ic2VydmF0aW9ucyBieSBmaWx0ZXJpbmcgb3V0IHRoZSBvYnNlcnZhdGlvbnMgZnJvbSB0aGUgc3RhdGUgb2YgaW50ZXJlc3QgZnJvbSBgYWxsU3RhdGVzYCBhbmQgdXNpbmcgYW4gYHJiaW5kYCBjb21tYW5kIHRvIGFwcGVuZCB0aGUgbmV3IE5KIGRhdGEgc2V0Lg0KDQo0LiBXcml0ZSB0aGUgZGF0YSB0byBmaWxlLg0KDQpgYGB7ciBjb2FsZXNjZV9vbmVfc3RhdGUsIGNhY2hlID0gVFJVRSwgZXZhbCA9IEZBTFNFfQ0KYWxsU3RhdGVzX3VwZGF0ZWQgPC0gcmVhZC5jc3YoImZpbGVwYXRoIGdvZXMgaGVyZSIpICU+JQ0KICBkcGx5cjo6c2VsZWN0KGNhbm9uaWNhbF9uYW1lcykgJT4lDQogIG11dGF0ZV9pZihpcy5mYWN0b3IsIGFzLmNoYXJhY3RlcikgJT4lDQogIG11dGF0ZShhY2NvdW50ID0gYXMuY2hhcmFjdGVyKGFjY291bnQpKSAlPiUNCiAgbXV0YXRlKGV4cGlyYXRpb25fZGF0ZSA9IHltZChleHBpcmF0aW9uX2RhdGUpLA0KICAgICAgICAgcHVibGlzaF9kYXRlID0geW1kKHB1Ymxpc2hfZGF0ZSkpICU+JQ0KICBmaWx0ZXIoc3RhdGUgIT0gIk5KIikgJT4lDQogIHJiaW5kKC4sIGdlb2NvZGVkX25qX2ZpeGVkKQ0KDQp3cml0ZS5jc3YoYWxsU3RhdGVzX3VwZGF0ZWQsICJmaWxlcGF0aCBnb2VzIGhlcmUiKQ0KYGBgDQoNCiMgMTAuIE1ha2UgQSBMZWFmbGV0IE1hcA0KDQpUaGUgZGF0YSBjYW4gYmUgdmlzdWFsaXplZCB1c2luZyB0aGUgYGxlYWZsZXRgIHdlYm1hcHBpbmcgcGFja2FnZSBmb3IgUiBNYXJrZG93bi4gVGhlIGZvbGxvd2luZyBtYXAgc3ViZGl2aWRlcyB0aGUgZGF0YSBpbnRvIGFjdGl2ZSBhbmQgZXhwaXJlZCBsaWNlbnNlcyAoc2VsZWN0YWJsZSB1c2luZyBhIG1lbnUgYXQgdGhlIHRvcCByaWdodCkgYW5kIGFsbG93cyB0aGUgdXNlciB0byBob3ZlciBvdmVyIGFueSBwb2ludCBhbmQgdmlldyB0aGUgbmFtZSBhbmQgYWRkcmVzcyBvZiB0aGUgbGljZW5zZSBob2xkZXIuIFRoZSBiYXNlbWFwcyBhcmUgb3BlbiBzb3VyY2UgYW5kIGF2YWlsYWJsZSBpbiB0aGUgYGxlYWZsZXQucHJvdmlkZXJzYCBsaWJyYXJ5Lg0KDQpBZGRpdGlvbmFsIGZlYXR1cmVzIGNhbiBiZSBhZGRlZCB0byB0aGUgbWFwLCBpbmNsdWRpbmcgYnV0IG5vdCBsaW1pdGVkIHRvIHNlYXJjaCBmdW5jdGlvbnMsIHBvbHlnb24gZ2VvbWV0cmllcywgYW5kIG1vcmUuIA0KDQpSLU1hcmtkb3duIGRvY3VtZW50cyBjYW4gYmUgYGtuaXRgIGVhc2lseSBpbnRvIHBvcnRhYmxlIGh0bWwgZmlsZXMgd2hpY2ggY2FuIGJlIHNlbnQgYW5kIHJlY2VpdmVkIGVhc2lseSBkdWUgdG8gdGhlaXIgcmVsYXRpdmVseSBzbWFsbCBmaWxlIHNpemUuDQoNCkZvciBlbGFib3JhdGUgZGF0YSBkYXNoYm9hcmRpbmcsIFItU2hpbnkgcHJvdmlkZXMgbW9yZSByb2J1c3Qgam9pbmluZyBhbmQgc2VsZWN0aW5nIGZ1bmN0aW9uYWxpdHksIGludGVncmF0ZWQgd2l0aCBhY3RpdmUgY2hhcnRzIGFuZCB0YWJsZXMuDQoNCmBgYHtyIGxlYWZsZXRfbWFwLCBjYWNoZSA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KDQphY3RpdmUgPC0gYWxsU3RhdGVzICU+JQ0KICBmaWx0ZXIoZXhwaXJlZF95X24gPT0gIkFDVElWRSIpDQoNCmV4cGlyZWQgPC0gYWxsU3RhdGVzICU+JQ0KICBmaWx0ZXIoZXhwaXJlZF95X24gIT0gIkFDVElWRSIpDQoNCm00IDwtIGxlYWZsZXQoKSAlPiUNCiAgc2V0VmlldyhsbmcgPSAtNzcuNDc1MTI3LCBsYXQgPSA0MC43MjY2MDQsIHpvb20gPSAwNykgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJEVzcmkuV29ybGRUb3BvTWFwKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2Vycyhsbmc9IGFzLm51bWVyaWMoYWN0aXZlJGxvbiksIA0KICAgICAgICAgICAgICAgICAgIGxhdD0gYXMubnVtZXJpYyhhY3RpdmUkbGF0KSwgDQogICAgICAgICAgICAgICAgICAgcmFkaXVzID0gMSwNCiAgICAgICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDEsDQogICAgICAgICAgICAgICAgICAgY29sb3I9ICJyZWQiLA0KICAgICAgICAgICAgICAgICAgIGxhYmVsID0gcGFzdGUoYWN0aXZlJHRyYWRlX25hbWUsICJ8IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2ZSRhZGRyZXNzX2Z1bGwpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSAiQWN0aXZlIFJldGFpbGVycyIpICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKGxuZz0gYXMubnVtZXJpYyhleHBpcmVkJGxvbiksIA0KICAgICAgICAgICAgICAgICAgIGxhdD0gYXMubnVtZXJpYyhleHBpcmVkJGxhdCksIA0KICAgICAgICAgICAgICAgICAgIHJhZGl1cyA9IDAuNSwNCiAgICAgICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDEsDQogICAgICAgICAgICAgICAgICAgY29sb3I9ICJibHVlIiwNCiAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHBhc3RlKGV4cGlyZWQkdHJhZGVfbmFtZSwgInwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZXhwaXJlZCRhZGRyZXNzX2Z1bGwpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSAiRXhwaXJlZCBSZXRhaWxlcnMiKSAlPiUNCiAgYWRkTGF5ZXJzQ29udHJvbCgNCiAgICBvdmVybGF5R3JvdXBzID0gYygiQWN0aXZlIFJldGFpbGVycyIsIA0KICAgICAgICAgICAgICAgICAgICAgICJFeHBpcmVkIFJldGFpbGVycyIpLA0KICAgIG9wdGlvbnMgPSBsYXllcnNDb250cm9sT3B0aW9ucyhjb2xsYXBzZWQgPSBUUlVFKSkgJT4lDQogIGFkZExlZ2VuZChjb2xvciA9ICJibHVlIiwgDQogICAgICAgICAgICBsYWJlbHMgPSAiRXhwaXJlZCBSZXRhaWxlcnMiLA0KICAgICAgICAgICAgZ3JvdXAgPSAiRXhwaXJlZCBSZXRhaWxlcnMiLA0KICAgICAgICAgICAgcG9zaXRpb24gPSAidG9wcmlnaHQiKSAlPiUNCiAgYWRkTGVnZW5kKGNvbG9yID0gInJlZCIsIA0KICAgICAgICAgICAgbGFiZWxzID0gIkFjdGl2ZSBSZXRhaWxlcnMiLA0KICAgICAgICAgICAgZ3JvdXAgPSAiQWN0aXZlIFJldGFpbGVycyIsDQogICAgICAgICAgICBwb3NpdGlvbiA9ICJ0b3ByaWdodCIpICAlPiUgDQogIGhpZGVHcm91cChjKCJFeHBpcmVkIFJldGFpbGVycyIpKSAlPiUNCiAgYWRkU2NhbGVCYXIocG9zaXRpb24gPSAidG9wbGVmdCIpDQoNCm00ICMgUHJpbnQgdGhlIG1hcA0KYGBgDQoNCiMgMTEuIERhdGEgZGljdGlvbmFyeQ0KDQojIyAxMS4xLiBEYXRhIGZyYW1lcyBnZW5lcmF0ZWQgZHVyaW5nIHVwZGF0aW5nIHJvdXRpbmVzDQoNCiMjIyAxMS4xLjEgUGVubnN5bHZhbmlhDQoNCmByZXRhaWxlcnNfU29jcmF0YV9wYWAgLSBEYXRhIGxvYWRlZCB2aWEgdGhlIFNPREEgQVBJIGZvciBQQQ0KDQpgc3RvcmVkX3BhYCAtIEJhc2VsaW5lIGRhdGEgc2V0DQoNCmBqb2luZWRfcGFgIC0gZnVsbCBqb2luIGJldHdlZW4gYHJldGFpbGVyc19Tb2NyYXRhX3BhYCBhbmQgYHN0b3JlZF9wYWANCg0KYHRvX2dlb2NvZGVfcGFgIC0gcmV0YWlsZXJzIGluIG5lZWQgb2YgYWRkcmVzcyBnZW9jb2RpbmcNCg0KYGdlb2NvZGVkX3BhYCAtIHJldGFpbGVycyB3aGljaCBoYXZlIGdvbmUgdGhyb3VnaCB0aGUgZ2VvY29kaW5nIHByb2Nlc3MNCg0KYGVycm9yc19wYWAgLSBkYXRhIHdoaWNoIG1pZ2h0IGhhdmUgYmFkIGdlb2NvZGVzIGFuZC9vciBoYXZlIHJlc2lzdGVkIGdlb2NvZGluZyBhbmQgbmVlZCB0byBiZSBtYW51YWxseSBnZW9jb2RlZC4NCg0KYGdlb2NvZGVkX3BhX2ZpeGVkYCAtIGRhdGEgZm9yIHdoaWNoIGJyb2tlbiBnZW9jb2RlcyBoYXZlIGJlZW4gbWFudWFsbHkgZml4ZWQNCg0KIyMjIDExLjEuMiBOZXcgSmVyc2V5DQoNCmByZXRhaWxlcnNfbmV3X25qYCAtIERhdGEgbG9hZGVkIHZpYSBleGNlbCBmcm9tIE5KIGdvdid0DQoNCmBzdG9yZWRfbmpgIC0gQmFzZWxpbmUgZGF0YSBzZXQNCg0KYGpvaW5lZF9uamAgLSBmdWxsIGpvaW4gYmV0d2VlbiBgcmV0YWlsZXJzX25ld19uamAgYW5kIGBzdG9yZWRfbmpgDQoNCmB0b19nZW9jb2RlX25qYCAtIHJldGFpbGVycyBpbiBuZWVkIG9mIGFkZHJlc3MgZ2VvY29kaW5nDQoNCmBnZW9jb2RlZF9uamAgLSByZXRhaWxlcnMgd2hpY2ggaGF2ZSBnb25lIHRocm91Z2ggdGhlIGdlb2NvZGluZyBwcm9jZXNzDQoNCmBlcnJvcnNfbmpgIC0gZGF0YSB3aGljaCBtaWdodCBoYXZlIGJhZCBnZW9jb2RlcyBhbmQvb3IgaGF2ZSByZXNpc3RlZCBnZW9jb2RpbmcgYW5kIG5lZWQgdG8gYmUgbWFudWFsbHkgZ2VvY29kZWQuDQoNCmBnZW9jb2RlZF9ual9maXhlZGAgLSBkYXRhIGZvciB3aGljaCBicm9rZW4gZ2VvY29kZXMgaGF2ZSBiZWVuIG1hbnVhbGx5IGZpeGVkDQoNCiMjIyAxMS4xLjMgRGVsYXdhcmUNCg0KYHJldGFpbGVyc19Tb2NyYXRhX2RlYCAtIERhdGEgbG9hZGVkIHZpYSB0aGUgU09EQSBBUEkgZm9yIERFDQoNCmBzdG9yZWRfZGVgIC0gQmFzZWxpbmUgZGF0YSBzZXQNCg0KYGpvaW5lZF9kZWAgLSBmdWxsIGpvaW4gYmV0d2VlbiBgcmV0YWlsZXJzX1NvY3JhdGFfZGVgIGFuZCBgc3RvcmVkX2RlYA0KDQpgdG9fZ2VvY29kZV9kZWAgLSByZXRhaWxlcnMgaW4gbmVlZCBvZiBhZGRyZXNzIGdlb2NvZGluZw0KDQpgZ2VvY29kZWRfZGVgIC0gcmV0YWlsZXJzIHdoaWNoIGhhdmUgZ29uZSB0aHJvdWdoIHRoZSBnZW9jb2RpbmcgcHJvY2Vzcw0KDQpgZXJyb3JzX2RlYCAtIGRhdGEgd2hpY2ggbWlnaHQgaGF2ZSBiYWQgZ2VvY29kZXMgYW5kL29yIGhhdmUgcmVzaXN0ZWQgZ2VvY29kaW5nIGFuZCBuZWVkIHRvIGJlIG1hbnVhbGx5IGdlb2NvZGVkLg0KDQpgZ2VvY29kZWRfZGVfZml4ZWRgIC0gZGF0YSBmb3Igd2hpY2ggYnJva2VuIGdlb2NvZGVzIGhhdmUgYmVlbiBtYW51YWxseSBmaXhlZA0KDQojIyAxMS4yLiBDYW5vbmljYWwgbmFtZXMgZm9yIGNvbHVtbnMgaW4gb3VyIGRhdGEgc2V0cw0KDQpgdHJhZGVfbmFtZWANCnR5cGU6IGBjaHJgDQpleGFtcGxlOiBgIjEyMjEgSU5DImANCg0KYGFjY291bnRgDQp0eXBlOiBgY2hyYA0KZXhhbXBsZTogYCIyMDA1MjExNTM0ImANCg0KYGV4cGlyZWRfeV9uYA0KdHlwZTogYGNocmANCmV4YW1wbGU6IGAiQUNUSVZFImANCg0KYGV4cGlyYXRpb25fZGF0ZWANCnR5cGU6IGBkYXRlYA0KZXhhbXBsZTogYDIwMjAtMTItMzFgDQoNCmBsYXRgDQp0eXBlOiBgY2hyYA0KZXhhbXBsZTogYCIzOS43ODkxNjQiYA0KDQpgbG9uYA0KdHlwZTogYGNocmANCmV4YW1wbGU6IGAiLTc1LjY5NzM4OCJgDQoNCmBhZGRyZXNzX2Z1bGxgDQp0eXBlOiBgY2hyYA0KZXhhbXBsZTogYCI3NDQxIExBTkNBU1RFUiBQSUtFIEhPQ0tFU1NJTiBERSAxOTcwNyJgDQoNCmBjb3VudHlgDQp0eXBlOiBgY2hyYA0KZXhhbXBsZTogYCJQSElMQURFTFBISUEiYA0KDQpgcHVibGlzaF9kYXRlYA0KdHlwZTogYGRhdGVgDQpleGFtcGxlOiBgMjAxOC0wMS0wMWANCg0KYGxpY2Vuc2VfdHlwZWANCnR5cGU6IGBjaHJgDQpleGFtcGxlOiBgIlRPQkFDQ08gUFJPRFVDVFMgUkVUQUlMRVIiYA0KDQpgc3RhdGVgDQp0eXBlOiBgY2hyYA0KZXhhbXBsZTogYCJERSJgDQoNCg0KIyAxMi4gVG8gZG8NCg0KTmV4dCBzdGVwcw0KDQotIFVwZGF0ZSBkYXRhIHNldHMgd2l0aCBuZXcgTkogZGF0YQ0KLSBSdW4gUEEgYW5kIERFIHVwZGF0ZXMNCi0gSW5jb3Jwb3JhdGUgdGhlc2UgbmV3IGRhdGEgaW50byBnZW9zY2FuIHJvdXRpbmVzDQotIEFkZCBOWVUgcm91dGluZXMgaW50byBvdXIgZXhwb3N1cmUgcm91dGluZXMgLSBzdGF5IGV2ZW50cyBldGMNCi0gQmVnaW4gYW5hbHl6aW5nIGV4aXN0aW5nIGNvbGxlY3Rpb25zIG9mIGRhdGENCi0gQWRkIG1vcmUgYnVpbHQgZW52aXJvbm1lbnQgYW5kIHJpc2stdGVycmFpbiBmdW5jdGlvbmFsaXR5DQoNCg==